Keep changed position of NVD3 legend after click - d3.js

I am trying to change nvd3 chart legend position to the bottom of the chart.
I've heard about the:
d3.select(.nv-legendWrap).attr("transform", "translate(x, y)")
It's actually working but a click action on the legend will replace it at the top of the chart.
And I don't get how to use de legendPostion function (that is not a function according to the console).

After doing this (mind the quotes):
d3.select(".nv-legendWrap").attr("transform", "translate(x,y)")
Which will, as you know, move the legend to another position, set the same position when you click it:
d3.select(".nv-legendWrap").on("click", function(){
d3.select(this).attr("transform", "translate(x,y)");
})
Here is an example, the original position of the legend is on the top, but I'm moving it to the bottom. Click it and it will remain in the same position:
var data = function() {
return stream_layers(3,10+Math.random()*100,.1).map(function(data, i) {
return {
key: 'Stream' + i,
values: data
};
});
}
/* Inspired by Lee Byron's test data generator. */
function stream_layers(n, m, o) {
if (arguments.length < 3) o = 0;
function bump(a) {
var x = 1 / (.1 + Math.random()),
y = 2 * Math.random() - .5,
z = 10 / (.1 + Math.random());
for (var i = 0; i < m; i++) {
var w = (i / m - y) * z;
a[i] += x * Math.exp(-w * w);
}
}
return d3.range(n).map(function() {
var a = [], i;
for (i = 0; i < m; i++) a[i] = o + o * Math.random();
for (i = 0; i < 5; i++) bump(a);
return a.map(stream_index);
});
}
/* Another layer generator using gamma distributions. */
function stream_waves(n, m) {
return d3.range(n).map(function(i) {
return d3.range(m).map(function(j) {
var x = 20 * j / m - i / 3;
return 2 * x * Math.exp(-.5 * x);
}).map(stream_index);
});
}
function stream_index(d, i) {
return {x: i, y: Math.max(0, d)};
}
nv.addGraph(function() {
var chart = nv.models.multiBarChart();
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.1f'));
chart.multibar.stacked(true); // default to stacked
chart.showControls(false); // don't show controls
d3.select('#chart svg')
.datum(data())
.transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
d3.select(".nv-legendWrap").attr("transform", "translate(0,340)");
d3.select(".nv-legendWrap").on("click", function(){
d3.select(this).attr("transform", "translate(0,340)");
})
return chart;
});
#chart svg {
height: 400px;
width: 600px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.5/nv.d3.min.js"></script>
<div id="chart">
<svg></svg>
</div>
Have in mind that the legendPosition method is quite limited, you can only choose top or right:
chart.legendPosition("top")
chart.legendPosition("right")

Related

Place several symbols on top of each candle on the canvas chart

Created a simplified example of what I need. There are 50 items in the series. Each item has a vertical list of five + symbols.
const data = [...Array(50).keys()];
const container = document.querySelector('d3fc-canvas');
const xScale = d3.scaleLinear().domain([0, data.length - 1]);
const yScale = d3.scaleLinear().domain(fc.extentLinear()(data));
const series = fc
.seriesCanvasPoint()
.xScale(xScale)
.yScale(yScale)
.crossValue((_, i) => i)
.mainValue(d => d)
.size((_, i) => 0)
.decorate((context, datum) => {
// Draw 5 symbols above current value
for (let i = 0; i < 5; i++) {
const y = yScale.invert(datum + i)
context.textAlign = 'center';
context.fillStyle = '#000';
context.font = '25px Arial';
context.fillText('+', 0, 0);
context.translate(0, y);
}
});
d3.select(container)
.on('draw', () => {
series(data);
})
.on('measure', () => {
const {
width,
height
} = event.detail;
xScale.range([0, width]);
yScale.range([height, 0]);
const ctx = container.querySelector('canvas').getContext('2d');
series.context(ctx);
});
container.requestRedraw();
body {
color: #1b1e23;
font-size: small;
font-family: sans-serif;
height: calc(100vh - 2em);
margin: 1em;
display: flex;
}
body>* {
flex: auto;
}
.domain,
.gridline-y,
.gridline-x,
.annotation-line>line {
stroke: currentColor;
stroke-opacity: 0.1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://unpkg.com/d3fc/build/d3fc.js"></script>
<d3fc-canvas use-device-pixel-ratio></d3fc-canvas>
Now, I'm trying to make it work with an actual financial chart and place these five + symbols vertically within each candle. It doesn't work because I don't know how to correctly convert Y value of each + to the coordinate system of the canvas.
const stream = fc.randomFinancial().stream();
const data = stream.take(20);
const scaleX = d3.scalePoint();
const scaleY = d3.scaleLinear();
const yExtent = fc.extentLinear().accessors([d => d.high, d => d.low]);
const xExtent = fc.extentLinear().accessors([d => d.date.getTime()]);
const gridlines = fc.annotationCanvasGridline();
const candlestick = fc.seriesCanvasCandlestick()
.crossValue((o, i) => o.date.getTime())
.lowValue((o, i) => o.low)
.highValue((o, i) => o.high)
.openValue((o, i) => o.open)
.closeValue((o, i) => o.close);
const points = fc
.seriesCanvasPoint()
.crossValue((o, i) => o.date.getTime())
.mainValue(d => d.close)
.size((_, i) => 0)
.decorate((context, datum) => {
const arrows = getMarks(datum.open, datum.close);
//context.translate(0, 0);
// Draw 5 symbols above current value
for (let i = 0; i < arrows.length; i++) {
const posCurrent = scaleY.invert(arrows[i])
//context.translate(0, 15);
context.translate(0, posCurrent); // move to current arrow's position
context.textAlign = 'center';
context.fillStyle = '#000';
context.font = '25px Arial';
context.fillText('+', 0, 0);
context.translate(0, -posCurrent); // reset and go back to start point
}
});
const multi = fc
.seriesCanvasMulti()
.series([gridlines, candlestick, points]);
const chart = fc
.chartCartesian(scaleX, scaleY)
.canvasPlotArea(multi);
function getMarks(open, close) {
let price = close;
const arrows = [];
const gap = Math.abs(close - open) / 5;
for (let i = 0; i < 5; i++) {
arrows.push(close > open ? price -= gap : price += gap);
}
return arrows;
}
function renderChart() {
data.push(stream.next());
data.shift();
scaleX.domain(data.map(o => o.date.getTime()));
scaleY.domain(yExtent(data));
chart
.xDomain(scaleX.domain())
.yDomain(scaleY.domain());
d3.select('#chart')
.datum(data)
.call(chart);
}
renderChart();
setInterval(renderChart, 1000);
#chart {
height: 500px;
}
<script src="https://unpkg.com/d3"></script>
<script src="https://unpkg.com/d3fc"></script>
<div id="chart"></div>
Is there a way to properly show these marks on top of the candle?
Cross-reference to Github for more details and examples.
https://github.com/d3fc/d3fc/issues/1635
Firstly, scale.invert is meant for when you have the pixel coordinate, but not the corresponding datum value. This is relevant when you are doing clicks on a screen, for example. In this case, it's not the right tool for the job. You already have access to datum, so you just need to call scaleY(datum.close).
However, d3fc uses canvas.translate() to change the zero point to .mainvalue(). Therefore, you cannot simply use scaleY(datum.close) to get the y-coordinate of the close value. Instead of (x, y), each point you want to draw becomes (0, y(arrow) - y(d.close)). So you need to change the logic to reflect this.
Needing to clean up after yourself with context.translate() is a recipe for bugs, It's almost always better to avoid such things.
As a final note, I had some confusion about your getMarks function. If you want to draw 5 arrows evenly distributed over your bar, you need to have a gap of 1/4th of the bar height. Think of it like trees on a street. If the trees at 10 feet apart and the street is 100 feet long, you'll have 11 trees, not 10. Because of this, I drew 6 plus signs, not 5, because otherwise it looked kind of off balance.
const stream = fc.randomFinancial().stream();
const data = stream.take(20);
const scaleX = d3.scalePoint();
const scaleY = d3.scaleLinear();
const yExtent = fc.extentLinear().accessors([d => d.high, d => d.low]);
const xExtent = fc.extentLinear().accessors([d => d.date.getTime()]);
const gridlines = fc.annotationCanvasGridline();
const candlestick = fc.seriesCanvasCandlestick()
.crossValue((o, i) => o.date.getTime())
.lowValue((o, i) => o.low)
.highValue((o, i) => o.high)
.openValue((o, i) => o.open)
.closeValue((o, i) => o.close);
const points = fc
.seriesCanvasPoint()
.crossValue((o, i) => o.date.getTime())
.mainValue(d => d.close)
.size((_, i) => 0)
.decorate((context, datum) => {
const arrows = getMarks(datum.open, datum.close);
const top = scaleY(datum.close);
// We need a little bit of offset, because the "+" is 20 pixels high
// and we want it in the middle of the point, not above it.
const offset = 10;
// Draw 5 symbols above current value
for (let i = 0; i < arrows.length; i++) {
context.textAlign = 'center';
context.fillStyle = '#000';
context.font = '25px Arial';
context.fillText('+', 0, scaleY(arrows[i]) - top + offset);
}
});
const multi = fc
.seriesCanvasMulti()
.series([gridlines, candlestick, points]);
const chart = fc
.chartCartesian(scaleX, scaleY)
.canvasPlotArea(multi);
function getMarks(open, close) {
let price = Math.min(open, close);
const arrows = [];
const gap = Math.abs(close - open) / 5;
for (let i = 0; i < 6; i++) {
arrows.push(price + i * gap);
}
return arrows;
}
function renderChart() {
data.push(stream.next());
data.shift();
scaleX.domain(data.map(o => o.date.getTime()));
scaleY.domain(yExtent(data));
chart
.xDomain(scaleX.domain())
.yDomain(scaleY.domain());
d3.select('#chart')
.datum(data)
.call(chart);
}
renderChart();
setInterval(renderChart, 1000);
#chart {
height: 500px;
}
<script src="https://unpkg.com/d3"></script>
<script src="https://unpkg.com/d3fc"></script>
<div id="chart"></div>

Setting background image in canvas animation

I need to set background image for this canvas animation without affecting the animation style.
This CodePen is shown below.
var c = document.getElementById('canv');
var $ = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var grav = 0.00095;
var s = [20, 15, 10, 5];
var gravX = w / 2;
var gravY = h / 2;
var nodes;
var num = 55;
var minDist = 155;
var spr = 0.0000009;
part();
run();
//random size function
function S() {
var curr = s.length;
var cur_ = Math.floor(Math.random() * curr);
return s[cur_];
}
function part() {
nodes = [];
for (var i = 0; i < num; i++) {
var node = {
hue: Math.random()*360,
rad: S(),
x: Math.random() * w,
y: Math.random() * h,
vx: Math.random() * 8 - 4,
vy: Math.random() * 8 - 4,
upd: function() {
this.x += this.vx;
this.y += this.vy;
if (this.x > w) this.x = 0;
else if (this.x < 0) this.x = w;
if (this.y > h) this.y = 0;
else if (this.y < 0) this.y = h;
},
draw: function() {
//outer ring
var g = $.createRadialGradient(this.x, this.y, this.rad * 2, this.x, this.y, this.rad);
g.addColorStop(0,'hsla(242, 55%, 15%,.7)');
g.addColorStop(.5, 'hsla(242, 50%, 10%,.5)');
g.addColorStop(1,'hsla(242, 30%, 5%,.5)');
$.fillStyle = g;
$.beginPath();
$.arc(this.x, this.y, this.rad * 2, 0, Math.PI * 2, true);
$.fill();
$.closePath();
//inner particle
var g2 = $.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.rad);
g2.addColorStop(0, 'hsla('+this.hue+', 85%, 40%, 1)');
g2.addColorStop(.5, 'hsla('+this.hue+',95%, 50%,1)');
g2.addColorStop(1,'hsla(0,0%,0%,0)');
$.fillStyle = g2;
$.beginPath();
$.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
$.fill();
$.closePath();
}
};
nodes.push(node);
}
}
function run() {
$.globalCompositeOperation = 'source-over';
$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.fillRect(0, 0, w, h);
$.globalCompositeOperation = 'lighter';
for (i = 0; i < num; i++) {
nodes[i].upd();
nodes[i].draw();
}
for (i = 0; i < num - 1; i++) {
var n1 = nodes[i];
for (var j = i + 1; j < num; j++) {
var n2 = nodes[j];
Spr(n1, n2);
}
Grav(n1);
}
window.requestAnimationFrame(run);
}
function Spr(na, nb) {
var dx = nb.x - na.x;
var dy = nb.y - na.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
$.lineWidth = 1;
$.beginPath();
$.strokeStyle = "hsla(217, 95%, 55%, .15)";
$.moveTo(na.x, na.y);
$.lineTo(nb.x, nb.y);
$.stroke();
$.closePath();
var ax = dx * spr;
var ay = dy * spr;
na.vx += ax;
na.vy += ay;
nb.vx -= ax;
nb.vy -= ay;
}
}
function Grav(n) {
n.vx += (gravX - n.x) * grav;
n.vy += (gravY - n.y) * grav;
};
window.addEventListener('resize', function() {
c.width = w = window.innerWidth;
c.height = h = window.innerHeight;
});
body{
width:100%;
margin:0;
overflow:hidden;
}
<canvas id='canv' ></canvas>
CSS
Just replace the beginning of the run() code to:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
...
Then move the color settings to CSS together with an image reference:
#canv {
background: hsla(242, 40%, 5%, .85) url(path/to/image.jpg);
}
Add background-size to the CSS rule if needed. Note that since you're using different blending modes such as lighter which depends on existing content, you may not get desired result as it will blend with an empty canvas and not a solid - the approach below should solve that in this case.
CodePen
JavaScript
As before, replace the first lines in run() but after you made sure the image you want to use has loaded, simply draw it in:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
If your image contains transparency you also need to clear the canvas first:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
CodePen

How do i bind onclick event to piechart segment?

How do i bind onclick event to piechart segment?
https://github.com/sauminkirve/HTML5/blob/master/PieChart/piechart.html
A pie chart segment is really a wedge. You have several ways to hit-test a wedge.
One way is the math way:
Test if the mouse is within the radius of a circle created by the wedges.
If the radius test is true, then calculate the angle of the mouse versus the circle's centerpoint.
Compare that angle to each wedge. If the angle is between the starting and ending angle of a specific wedge's arc, then the mouse is inside that wedge.
Another way is to use canvas's built in path hit-testing method: isPointInPath
Redefine one wedge. There's no need to actually stroke or fill that wedge. Just do the commands from beginPath to closePath.
Use context.isPointInPath(mouseX,mouseY) to hit-test if the mouse is inside that wedge.
If isPointInPath returns true, you've discovered the wedge under the mouse. If not, then redefine & hit-test each of the other wedges.
Here's something I coded a while back that hit-tests the wedges of a pie chart when hovering and moves the wedge out of the pie when a wedge is clicked.
It uses the isPointInPath method to do the hit-testing:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.lineJoin = "round";
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
function Wedge(cx, cy, radius, startAngleDeg, endAngleDeg, fill, stroke, linewidth) {
this.cx = cx;
this.cy = cy;
this.radius = radius;
this.startAngle = startAngleDeg * Math.PI / 180;
this.endAngle = endAngleDeg * Math.PI / 180;
this.fill = fill;
this.stroke = stroke;
this.lineWidth = linewidth;
this.offsetX = 0;
this.offsetY = 0;
this.rr = radius * radius;
this.centerX = cx;
this.centerY = cy;
this.midAngle = this.startAngle + (this.endAngle - this.startAngle) / 2;
this.offsetDistance = 15;
this.explodeX = this.offsetDistance * Math.cos(this.midAngle);
this.explodeY = this.offsetDistance * Math.sin(this.midAngle);
this.isExploded = false;
};
Wedge.prototype.draw = function(fill, stroke) {
this.define();
this.fillStroke(fill, stroke);
ctx.beginPath();
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.lineWidth = 0.50;
ctx.stroke();
}
Wedge.prototype.fillStroke = function(fill, stroke) {
ctx.fillStyle = fill || this.fill;
ctx.fill();
ctx.strokeStyle = stroke, this.stroke;
ctx.lineWidth = this.lineWidth;
ctx.stroke();
}
Wedge.prototype.define = function() {
var x = this.cx + this.offsetX;
var y = this.cy + this.offsetY;
ctx.beginPath();
ctx.arc(x, y, this.radius, this.startAngle, this.endAngle);
ctx.lineTo(x, y);
ctx.closePath();
}
Wedge.prototype.ptAtAngle = function(radianAngle) {
var xx = (this.cx + this.offsetX) + this.radius * Math.cos(radianAngle);
var yy = (this.cy + this.offsetY) + this.radius * Math.sin(radianAngle);
return ({
x: x,
y: y
});
}
Wedge.prototype.explode = function(isExploded) {
this.isExploded = isExploded;
this.offsetX = isExploded ? this.explodeX : 0;
this.offsetY = isExploded ? this.explodeY : 0;
this.draw();
}
Wedge.prototype.isPointInside = function(x, y) {
var dx = x - (this.cx + this.offsetX);
var dy = y - (this.cy + this.offsetY);
if (dx * dx + dy * dy > this.rr) {
return (false);
}
var angle = (Math.atan2(dy, dx) + Math.PI * 2) % (Math.PI * 2);
return (angle >= this.startAngle && angle <= this.endAngle);
}
Wedge.prototype.marker = function(pos) {
ctx.beginPath();
ctx.arc(pos.x, pos.y, 3, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
}
function handleMouseDown(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
clear();
for (var i = 0; i < wedges.length; i++) {
var wedge = wedges[i].wedge;
if (wedge.isPointInside(mouseX, mouseY)) {
wedge.explode(!wedge.isExploded);
}
wedge.draw();
}
}
function handleMouseUp(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mouseup stuff here
isDown = false;
}
function handleMouseOut(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mouseOut stuff here
isDown = false;
}
function handleMouseMove(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
for (var i = 0; i < wedges.length; i++) {
var wedge = wedges[i].wedge;
if (wedge.isPointInside(mouseX, mouseY)) {
wedge.draw("black");
} else {
wedge.draw();
}
}
}
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function(e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function(e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function(e) {
handleMouseOut(e);
});
function clear() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
var PI2 = Math.PI * 2;
var cx = 150;
var cy = 150;
var r = 100;
var line = 2;
var stroke = "black";
var wedges = [];
wedges.push({
percent: 18,
fill: "red"
});
wedges.push({
percent: 30,
fill: "blue"
});
wedges.push({
percent: 25,
fill: "green"
});
wedges.push({
percent: 13,
fill: "purple"
});
wedges.push({
percent: 14,
fill: "gold"
});
var rAngle = 0;
for (var i = 0; i < wedges.length; i++) {
var wedge = wedges[i];
var angle = 360 * wedge.percent / 100;
wedge.wedge = new Wedge(cx, cy, r, rAngle, rAngle + angle, wedge.fill, "black", 1);
wedge.wedge.draw();
rAngle += angle;
}
window.onscroll = function(e) {
var BB = canvas.getBoundingClientRect();
offsetX = BB.left;
offsetY = BB.top;
}
body {
background-color: ivory;
}
#canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Hover wedge to highlight it<br>Click wedge to explode that wedge</h4>
<canvas id="canvas" width=300 height=300></canvas>

Improving D3 Sequence sunburst to have zooming technique

I have a normal sequence sunburst jsfiddle as you can it is working normally.
I am trying to improve this sunburst to get a zooming technique.
with the following code I can et one step zooming,
var path = vis.data([json]).selectAll("path")
.
.
.on("click", click)
.each(stash);
the click function is:
function click(d)
also the code has the arctween, stash and reposition functions as below
function arcTween(a){
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
}
function stash(d) {
d.x0 =0;// d.x;
d.dx0 =0;// d.dx;
}
function reposition(node, x, k) {
node.x = x;
if (node.children && (n = node.children.length)) {
var i = -1, n;
while (++i < n) x += reposition(node.children[i], x, k);
}
return node.dx = node.value * k;
}
this is the final version of my sunburst with zooming JSfiddle
the Question is: How can I make the sequences sunburst works with zooming approach?
and how to stop zoomin and o back as [bilevel or zoomable sunburst]

Capturing the mouse coordinates on every step of a jQuery animation

I'm trying to get the mouse coordinates during a jQuery animation, I'm doing this because I'm working on an interactive plug-in which moves the background image inside a div from covercss property to 100% of it's scale when the user go over the element.
I'm near to completing the plug-in but the animation is buggy because it work on the last position of the mouse fired by mousemove event of jQuery.
Does exists some way to avoid the problem?
This is my situation:
$(settings.selector).hover(function (e) {
$(this).bind('mousemove', setFollowMouse);
}, function () {
$(this).unbind('mousemove', setFollowMouse);
zoomOut();
});
var setFollowMouse = function (e) {
var o = {offsetLeft:this.offsetLeft, offsetTop:this.offsetTop};
if (!settings.bg.zooming_in && settings.bg.current_scale != 100) {
settings.bg.zooming_in = true;
zoomIn(e, o);
} else {
followMouse(e, o);
}
}
var zoomIn = function (e, o) {
$({scale:settings.bg.min_perc}).animate ({
scale:100
},{
easing:settings.zoom_in.easing,
duration:settings.zoom_in.duration,
step:function () {
settings.bg.current_scale = this.scale;
followMouse(e, o);
},
complete:function () {
settings.bg.current_scale = 100;
settings.bg.zooming_in = false;
followMouse(e, o);
}
});
}
var followMouse = function (e, o) {
var m_x = e.pageX - o.offsetLeft;
var m_y = e.pageY - o.offsetTop;
settings.bg.perc_pos_x = ((m_x * 100) / (a_w - bg_w)) + '%';
settings.bg.perc_pos_y = ((m_y * 100) / (a_h - bg_h)) + '%';
var bg_w = getScalePercent(settings.bg.width, settings.bg.current_scale);
var a_w = settings.container.width;
var bg_h = getScalePercent(settings.bg.height, settings.bg.current_scale);
var a_h = settings.container.height;
var bpx = - (bg_w - a_w) * m_x / a_w;
var bpy = - (bg_h - a_h) * m_y / a_h;
$(settings.selector).css({
backgroundPosition:bpx + 'px ' + bpy + 'px'
,backgroundSize:bg_w + 'px ' + bg_h + 'px'
});
}
As you see, I use animation to calculate the progressive scaling of the background-image, and trying to calculating it with the follow mouse method, but if I sto moving the mouse, the animation works with the last mousemove event.pageX and Y mouse position.
I've done this because I have problems with make animation method fluid if I trying to rewrite it continuously by with the mouse.
Should I follow some different way to avoid the bug?
forgive my dodgy math; but this should help!
<html>
<head>
<script type="text/javascript" charset="utf-8">
window.onload = function () {
var img = new Image();
img.src = 'http://wallpapers.free-review.net/wallpapers/23/Autumn_Theme_-_Windows_7_Backgrounds.jpg';
var canvas = document.getElementById("canvas1");
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousemove', onMouseMove, false);
var ctx = canvas.getContext("2d");
var scale = 0.9;
var scaledwidth = canvas.width * scale;
var scaledheight = canvas.height * scale;
var scaledcenterX = scaledwidth /2;
var scaledcenterY = scaledheight /2;
var animloop = setInterval(function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, scaledwidth, scaledheight, canvas.width - scaledcenterX, canvas.height - scaledcenterY, 0, 0, canvas.width, canvas.height);
}, .01);
function onMouseMove(e) {
mouseX = e.clientX - canvas.offsetLeft;
mouseY = e.clientY - canvas.offsetTop;
scale = mouseX/1000;
scaledwidth = canvas.width * scale;
scaledheight = canvas.height * scale;
}
};
</script>
<style>
body {
background: #001;
background-size: cover;
overflow: hidden;
}
#canvas1 {
position: absolute;
top: 0;
left: 0;
padding: 0;
margin: 0;
height: 100% auto;
}
</style>
</head>
<body>
<canvas id="canvas1"></canvas>
</body>
</html>
I've just solved the problem with this simple edit to my code:
var setFollowMouse = function (e) {
settings.mouse.x = e.pageX - this.offsetLeft;
settings.mouse.y = e.pageY - this.offsetTop;
if (!settings.bg.zooming_in && settings.bg.current_scale != 100) {
settings.bg.zooming_in = true;
zoomIn();
} else {
followMouse();
}
}
the old one:
var setFollowMouse = function (e) {
var o = {offsetLeft:this.offsetLeft, offsetTop:this.offsetTop};
if (!settings.bg.zooming_in && settings.bg.current_scale != 100) {
settings.bg.zooming_in = true;
zoomIn(e, o);
} else {
followMouse(e, o);
}
}
this has removed the buggy behavior.

Resources