vis graph2d y-axis ints only - javascript

Is there a way to make the y-axis only have integer-based tick marks. This would be a tremendous help. Also, if the tick marks could be only at multiples of
2 when 0 <= window height <= 10
10 when 10 < window height <= 100
100 window height < 100
... that would be nice - though not absolutely necessary. Sorry for the pseudo-code.
Right now I am doing the following and it works ok, but it is not great.
this.dataset = new vis.DataSet();
var options = {
start: vis.moment(),
end: vis.moment().add(50, 'seconds'),
showMajorLabels: false,
zoomable: true,
dataAxis: {
left: {
range: {
min: 0
}
},
}
style: 'bar',
drawPoints: false,
}
this.graph = new vis.Graph2d(container/*assume I made it :)*/, this.dataset, options);
//+~~~~~~~~~~+
//| LATER ON |
//+~~~~~~~~~~+
range = this.graph.getWindow();
var current = this.dataset.get({
filter: function(item) {
return item.x > range.start && item.x < range.end;
}
});
var maxY = 0;
for (var i = 0, currentLen = current.length; i < currentLen; i++) {
if (current[i].y > maxY) {
maxY = current[i].y;
}
}
maxY = maxY < 8 ? Math.floor((maxY + 4) / 4) * 4 : Math.floor((maxY + 6) / 6) * 6; // makes tick marks at places that don't cause decimal values #_#
this._graph.setOptions({
dataAxis: {
left: {
range: {
max: maxY,
}
}
}
});
Quick note: the line maxY = maxY < 8 ? ... only works for 1-11 from what I can test. I am not sure about higher numbers. However, hopefully this can be fixed.

Very very late answer, but assuming you only care about the y-axis labels themselves and not the horizontal grid lines, you can do this:
var options = {
dataAxis: {
left: {
format: function(value){
// don't show non-integer values on data axis
return Math.round(value) === value ? value : "";
}
}
}
}
You can also access window.height in that function and adjust accordingly for the behaviors you described.

So, it seems like the method I implemented works (e.g. I only see whole numbers regardless of the y value) if you want ints only and don't mind (or want) the minimum y value to be 4 and, additionally, don't mind what the step is.
However, note that this has only been tested with [0, inf) (positive, numbers or 0).
If you have a solution that uses vis to do this - rather than checking for all of the max y values manually - please do share in an answer.

Related

Generalize find smallest size solution for all four sides of rectangle

In an app, a user can click the edge of a rectangular box to resize it to the "smallest valid size" in that direction. For example, if they clicked the right edge, the box would resize to the smallest valid width, leaving the left edge where it is. If they clicked the bottom, it would shrink the bottom edge as far as possible without becoming invalid. The criteria for what "valid" is doesn't matter, but it takes some work to calculate.
Now I have written some code to make this work for the right edge of the box, using a binary search to close in on the smallest valid size. But I need a general solution which does not involve repeating my code in four different variations, and I can't figure out how to do it.
The code as it is now (supporting right edge only):
interface Zone {
x: number;
y: number;
width: number;
height: number;
}
enum Side {
'top',
'left',
'bottom',
'right',
}
enum ResizeDirection {
NEGATIVE = -1,
POSITIVE = 1,
}
async function findMinValidZone(
zone: Zone,
minWidth: number,
maxWidth: number,
side: Side,
validator: (z: Zone) => Promise<boolean>
): Promise<Zone> {
const area = {...zone};
let d = maxWidth;
let step = maxWidth - minWidth;
let currentDirection = ResizeDirection.NEGATIVE; // positive means widen the area, negative means narrow it
let lastGood: number = d;
do {
d += step * currentDirection;
if (d < minWidth || d > maxWidth) {
break;
}
area.width = d;
const ok = await validator(area);
if (ok) {
currentDirection = ResizeDirection.NEGATIVE;
lastGood = d;
} else {
currentDirection = ResizeDirection.POSITIVE;
}
step = step / 2;
} while (step >= 1);
area.width = lastGood;
return area;
}
One way I can think to do this is to rotate the rectangle by whatever multiple of 90 degrees is required to transform the rectangle so that you are always working with the right edge, than rotate it back for the validation and when returning the result. The actual rotation could be done using matrix transformations, but for now I'll leave the actual rotate function for you to write. Then your generalised code would look like this:
function findMinValidZone(
zone: Zone,
minwidth: number,
maxWidth: number,
side: Side,
validator: (z: Zone) => Promise<boolean>
): Promise<Zone> {
let rotationAngle: number;
switch (side) {
case Side.bottom:
rotationAngle = -Math.PI / 2;
break;
case Side.left:
rotationAngle = Math.PI;
break;
case Side.right:
rotationAngle = 0;
break;
case Side.top:
rotationAngle = Math.PI / 2;
}
let d = maxWidth;
let step = maxWidth - minwidth;
// rotate the area so that you are always working on the width dimension
// while keeping other dimensions fixed. Then rotate area back again
// when validating and when returning the results
const area = rotateZone({...zone}, rotationAngle);
let currentDirection = ResizeDirection.NEGATIVE; // positive means widen the area, negative means narrow it
let lastGood: number = d;
do {
d += step * currentDirection;
if (d < minwidth || d > maxWidth) {
break;
}
area.width = d;
const ok = await validator(rotateZone(area, -rotationAngle));
if (ok) {
currentDirection = ResizeDirection.NEGATIVE;
lastGood = d;
} else {
currentDirection = ResizeDirection.POSITIVE;
}
step = step / 2;
} while (step >= 1);
area.width = lastGood;
return rotateZone(area, -rotationAngle);
}
I'll think about the rotateZone function itself, but in principle it should be simple enough using a library like https://www.npmjs.com/package/transformation-matrix

JS Optimization - constantly setting variables on fast fire events

I am working on a script using Three.js where a lot a variables depend on mouse position. Does it matter if I am constantly setting variables to their same value each time the move event fires, or should I only set a variable when a change is detected?
Let's say I want to set a variable "quadrant" to 1,2,3 or 4 depending upon which part of the screen the mouse is over... should I use this :
var quadrant;
function mouseMove(e){
var mouse;
mouse.x = e.clientX;
mouse.y = e.clientY;
if(mouse.x < window.innerWidth / 2){
if(mouse.y < window.innerHeight / 2){
quadrant = 1;
} else {
quadrant = 3;
}
} else {
if(mouse.y < window.innerHeight / 2){
quadrant = 2;
} else {
quadrant = 4;
}
}
};
window.addEventListener('mousemove', mouseMove);
Which will reset the variable every time the event fires. Or should I only be setting variables when a change is detected, like this :
var quadrant;
function mouseMove(e){
var mouse;
mouse.x = e.clientX;
mouse.y = e.clientY;
if(mouse.x < window.innerWidth / 2){
if(mouse.y < window.innerHeight / 2){
if(quadrant != 1){
quadrant = 1;
}
} else {
if(quadrant != 3){
quadrant = 3;
};
}
} else {
if(mouse.y < window.innerHeight / 2){
if(quadrant != 2){
quadrant = 2;
};
} else {
if(quadrant != 4){
quadrant = 4;
};
}
}
};
window.addEventListener('mousemove', mouseMove);
Does the act of setting a variable to the memory (even if it's to the same value) cost more than it takes to read the extra lines of code necessary to add the conditions? I instinctively do the latter as it seems tidier and like less work at runtime, but I really have no idea how this actually translates to performance. I seem to remember reading that each time a variable is set in js that it's actually creating an instance of itself, which seems like work... but maybe I misunderstood.
As noted in the comments, the simpler version is very likely to be faster - and it's easier to read and less error-prone too.
While I've got you, let me suggest a completely different approach: calculate the quadrant instead of using a bunch of if statements.
// Calculate the quadrant for a given x and y and width and height.
// The quadrants are defined like this:
//
// +---+---+
// | 1 | 2 |
// +---+---+
// | 3 | 4 |
// +---+---+
function getQuadrant( x, y, width, height ) {
return 1 +
( x >= width / 2 ) +
( y >= height / 2 ) * 2;
}
console.log( getQuadrant( 25, 25, 100, 100 ) ); // 1
console.log( getQuadrant( 75, 25, 100, 100 ) ); // 2
console.log( getQuadrant( 25, 75, 100, 100 ) ); // 3
console.log( getQuadrant( 75, 75, 100, 100 ) ); // 4
This code works because when you use an arithmetic operator on a boolean value, it converts a false value to 0 and a true value to 1.
I don't know if this will be faster or slower (you would have to benchmark it to find out) but since you are looking at different approaches to solving the problem, I thought you might find it interesting.
You may wonder "aren't those multiplies and divides slow?" But modern JavaScript engines, like most optimizing compilers, can convert multiplies or divides by a power of 2 into a very fast bit-shifting operation.
Let's take a look at the machine code that V8 generates for the getQuadrant function (just showing the core part of the code, not the function setup and teardown).
When we enter this code, the four function parameters are stored in these registers:
r8 is x.
r11 is y.
rdx is width.
rdi is height.
And here's the compiled code:
; Divide height and width by 2 for the comparisons below
sarl rdi, 1
sarl rdx, 1
; Compare y with half the height and set rcx to 0 or 1
cmpl rdi,r11
setlel cl
movzxbl rcx,rcx
; Compare x with half the width and set rdx to 0 or 1
cmpl rdx,r8
setlel dl
movzxbl rdx,rdx
; Set rdx to the final result, calculated in a single instruction
leal rdx,[rdx+rcx*2+0x1]
One likely performance advantage is that this code avoids the branches used by the if statements. On modern CPUs, when you can avoid branches, it is often a performance win.
But again, any of these approaches will likely be more than fast enough! Just posting this alternative method in case it is of interest to you.
If you're curious how I got that machine code listing, I created a standalone JavaScript file called quadrants.js with this content:
function getQuadrant( x, y, width, height ) {
return 1 +
( x >= width / 2 ) +
( y >= height / 2 ) * 2;
}
// We need to actually do something with the result returned by getQuadrant,
// otherwise the JavaScript engine may notice that the result is unused and
// it may skip compiling the function body entirely.
quadrants = [];
for( let i = 0; i < 1000000; ++i ) {
quadrants.push( getQuadrant( 25, 25, 100, 100 ) );
quadrants.push( getQuadrant( 75, 25, 100, 100 ) );
quadrants.push( getQuadrant( 25, 75, 100, 100 ) );
quadrants.push( getQuadrant( 75, 75, 100, 100 ) );
}
// Log the first few results as a sanity check
console.log( quadrants.length );
for( let i = 0; i < 16; ++i ) {
console.log( quadrants[i] );
}
Then I ran it with this command:
node --print-opt-code --code-comments quadrants.js >code.txt
And then I looked through the generated code.txt file to find the code for the getQuadrant function.
Performance wise, they should be very similar. However it really depends on what happens after setting the variable. Are you going to call a function that do hefty work each time? Then you're better of using the second one.
You shouldn't bother yourself with micro-optimizations, A couple milliseconds delay won't really affect your application.
Also if you need to see for yourself here's some benchmark code to run (It won't be that accurate though). It shows the average time in seconds for running each function 1k times
let sum1 = 0, sum2 = 0, quadrant;
for(i = 0; i < 1000; i++){
let obj = calculate(1000);
sum1 += obj.t1;
sum2 += obj.t2;
}
console.log("avg for first: ", sum1 / 1000);
console.log("avg for second: ", sum2 / 1000);
function calculate(numIterations){
//first function
let start = Date.now();
for(let i = 0; i < numIterations; i++){
mouseMove(generateEventObject());
}
let t1 = (Date.now() - start) / 1000;
//second function
start = Date.now();
for(let i = 0; i < numIterations; i++){
mouseMove2(generateEventObject());
}
let t2 = (Date.now() - start) / 1000;
return {t1, t2}
}
function generateRandom(max) {
return Math.random() * max;
}
function generateEventObject() {
return {
clientX: generateRandom(window.innerWidth),
clientY: generateRandom(window.innerHeight)
}
}
function mouseMove(e){
var mouse = {};
mouse.x = e.clientX;
mouse.y = e.clientY;
if(mouse.x < window.innerWidth / 2){
if(mouse.y < window.innerHeight / 2){
quadrant = 1;
} else {
quadrant = 3;
}
} else {
if(mouse.y < window.innerHeight / 2){
quadrant = 2;
} else {
quadrant = 4;
}
}
};
function mouseMove2(e){
var mouse = {};
mouse.x = e.clientX;
mouse.y = e.clientY;
if(mouse.x < window.innerWidth / 2){
if(mouse.y < window.innerHeight / 2){
if(quadrant != 1){
quadrant = 1;
}
} else {
if(quadrant != 3){
quadrant = 3;
};
}
} else {
if(mouse.y < window.innerHeight / 2){
if(quadrant != 2){
quadrant = 2;
};
} else {
if(quadrant != 4){
quadrant = 4;
};
}
}
};

Simplify table code in EaselJs

I've got a 2x3 table that I'm adding to EaselJS...currently I'm building it like this:
for (var i = 0; i < 6; i++) {
if(i == 1 || i == 3 || i == 5) {
var xPos = playersBoxW;
} else {
var xPos = 0;
}
if(i == 2 || i == 3) {
var yPos = playersBoxH;
} else if (i == 4|| i == 5) {
var yPos = playersBoxH*2;
} else {
var yPos = 0;
}
playerBox[i] = new createjs.Container().set({x: xPos, y: yPos});
}
It just seems a very inefficient way of doing it and not useful if the table grows. Anyone else have an idea to simplify this?
If you are just trying to do row/column math, there is an easier way.
Here is your original example (with some code to make it work) http://jsfiddle.net/u3ds24y5/
You can just derive the column and row with a simple equation. This lets you change the number of columns and total count easily.
var column = i % num_columns;
var row = Math.floor(i / num_columns);
var x = column * column_width;
var y = row * row_height;
Here is an updated fiddle: http://jsfiddle.net/u3ds24y5/1/
Simplified code:
var cols = 2, total = 6; // Change these
for (var i = 0; i < total; i++) {
var xPos = i % cols * playersBoxW,
yPos = Math.floor(i/cols) * playersBoxH;
// Create container, etc
}
Looking at your code I think this algorithm is essentially what it boils down to:
xPos seems to be equal to the integer division of i (by table width) times playersBoxW. e.g if i = 3 and width is 2, then xPos is equal to playersBoxW times int division of 3/2 which is 1.
yPos seems to be equal to the integer division of i (by table height) times playersBoxH. e.g if i = 4 and height = 3, then yPos is equal to playersBoxH times int division of 4/3 which is 1.
function integerDivision(a, b) {
return Math.floor(a / b);
}
function makeTable(width, height, player, arr) {
var xPos, yPos, size = width*height;
for (var i = 0; i < size; i++) {
xPos = player.boxW * integerDivision(i, width);
yPos = player.boxH * integerDivision(i, height);
arr[i] = new createjs.Container().set({x: xPos, y: yPos});
}
return arr;
}
Integer division is like regular division but you throw the remainder away. So in this case we round the number down:
3/2 = 1.5 => floor the result (round down) => 1
Side node: EaslJS containers can be expensive sometimes so be careful with them.
Containers have some overhead, so you generally shouldn't create a Container to hold a single child. [easljs doc]

algorithm to randomly & efficiently place 100 circles without any overlap?

I am trying to write a script to place 100 circles of varying sizes onto a stage. I've outlined the concise requirements below.
Given the following:
var stage; // contains a "width" and "height" property.
var circle; // the circle class. contains x, y, radius & a unique id property.
var circleArray; // contains 100 circle instances
requirements:
write a function to place 100 circles of varying radius onto the stage.
placements must be random but evenly distributed (no clumping).
placement must be performant - this will be executing on a mobile web browser.
circles must not intersect/overlap other circles.
circle.x >= 0 must be true.
circle.y >= 0 && circle.y <= stage.height must be true.
circles may have any of the following radius sizes (assigned at creation):
150
120
90
80
65
My current attempt is a brute-force method, which does not operate efficiently. If I attempt to insert any more than ~10 circles, the browser hangs. Below is my current implementation, which I am completely OK with throwing away in favor of a more performant / better one.
Here is a live demo (NOTE: there is no actual drawing code, just the logic, but it will still lock up the browser so be warned!!) http://jsbin.com/muhiziduxu/2/edit?js,console
function adjustForOverlap (circleArray) {
// a reference to the circle that is invoking this function.
var _this = this;
// remove this circle from the array we are iterating over.
var arr = circleArray.filter(function (circle){
return circle.id !== _this.id;
});
// while repeat == true, the circle may be overlapping something.
var repeat = true;
while(repeat) {
var hasOverlap = false;
for (var i=0; i<arr.length; i++) {
var other = arr[i];
var dx = _self.x - other.x;
var dy = _self.y - other.y;
var rr = _self.radius + other.radius;
if (dx * dx + dy * dy < rr * rr) {
// if here, then an overlap was detected.
hit = true;
break;
}
}
// if hit is false, the circle didn't overlap anything, so break.
if (hit === false) {
repeat = false;
break;
} else {
// an overlap was detected, so randomize position.
_self.x = Math.random() * (stage.width*2);
_self.y = Math.random() * stage.height;
}
}
}
There are lots of efficient collision detection algorithms. Many of them work by dividing up the space into cells and maintaining a separate data structure with efficient lookup of other objects in the cell. The basic steps are:
Identify a random spot for your new circle
Determine which cells it's in
Look in each of those cells for a collision
If there's a collision, goto 1.
Else, add the new circle to each of the cells it overlaps.
You can use a simple square grid (i.e. a 2-d array) for the cell data structure, or something else like a quadtree. You can also in some cases get a bit of extra speed by trying a cheap-but-coarse collision check first (do the bounding boxes overlap), and if that returns true try the slightly more expensive and exact check.
Update
For quadtrees, check out d3-quadtree, which ought to give you a pretty good implementation, with examples.
For a (very quick, untested) 2-d array implementation:
function Grid(radius, width, height) {
// I'm not sure offhand how to find the optimum grid size.
// Let's use a radius as a starting point
this.gridX = Math.ceil(width / radius);
this.gridY = Math.ceil(height / radius);
// Determine cell size
this.cellWidth = width / this.gridX;
this.cellHeight = height / this.gridY;
// Create the grid structure
this.grid = [];
for (var i = 0; i < gridY; i++) {
// grid row
this.grid[i] = [];
for (var j = 0; j < gridX; j++) {
// Grid cell, holds refs to all circles
this.grid[i][j] = [];
}
}
}
Grid.prototype = {
// Return all cells the circle intersects. Each cell is an array
getCells: function(circle) {
var cells = [];
var grid = this.grid;
// For simplicity, just intersect the bounding boxes
var gridX1Index = Math.floor(
(circle.x - circle.radius) / this.cellWidth
);
var gridX2Index = Math.ceil(
(circle.x + circle.radius) / this.cellWidth
);
var gridY1Index = Math.floor(
(circle.y - circle.radius) / this.cellHeight
);
var gridY2Index = Math.ceil(
(circle.y + circle.radius) / this.cellHeight
);
for (var i = gridY1Index; i < gridY2Index; i++) {
for (var j = gridX1Index; j < gridX2Index; j++) {
// Add cell to list
cells.push(grid[i][j]);
}
}
return cells;
},
add: function(circle) {
this.getCells(circle).forEach(function(cell) {
cell.push(circle);
});
},
hasCollisions: function(circle) {
return this.getCells(circle).some(function(cell) {
return cell.some(function(other) {
return this.collides(circle, other);
}, this);
}, this);
},
collides: function (circle, other) {
if (circle === other) {
return false;
}
var dx = circle.x - other.x;
var dy = circle.y - other.y;
var rr = circle.radius + other.radius;
return (dx * dx + dy * dy < rr * rr);
}
};
var g = new Grid(150, 1000, 800);
g.add({x: 100, y: 100, radius: 50});
g.hasCollisions({x: 100, y:80, radius: 100});
Here's a fully-functional example: http://jsbin.com/cojoxoxufu/1/edit?js,output
Note that this only shows 30 circles. It looks like the problem is often unsolvable with your current radii, width, and height. This is set up to look for up to 500 locations for each circle before giving up and accepting a collision.

gRaphael linechart draws values beyond axis

When drawing a linechart with gRaphael using milliseconds along the x-axis I commonly get inconsistencies in the placement of the data points. Most commonly the initial data points are to the left of the y-axis (as seen in the fiddle below), sometimes the last data-point will be beyond the right side of the view-box/past the termination of the x-axis.
Does anyone know:
1) Why this occurs,
2) How to prevent it, &/or
3) How to check for it (I can use transform to move the lines/points if I know when it has happened/by how much).
my code:
var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var r2 = Raphael("holder2"),
txtattr2 = { font: "12px sans-serif" };
var x = [], y = [], y2 = [], y3 = [];
for (var i = 0; i < 1e6; i++) {
x[i] = i * 10;
y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];
var xVals = [1288885800000, 1289929440000, 1290094500000, 1290439560000, 1300721700000, 1359499228000, 1359499308000, 1359499372000];
var yVals = [80, 76, 70, 74, 74, 78, 77, 72];
var xVals2 = [1288885800000, 1289929440000];
var yVals2 = [80, 76];
var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines.symbols.attr({ r: 3 });
var lines2 = r2.linechart(10, 10, 300, 220, xVals2, yVals2, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r2.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines2.symbols.attr({ r: 3 });
I do have to use gRaphael and the x-axis has to be in milliseconds (it is labeled later w/customized date strings)
Primary example fiddle: http://jsfiddle.net/kcar/aNJxf/
Secondary example fiddle (4th example on page frequently shows both axis errors):
http://jsfiddle.net/kcar/saBnT/
root cause is the snapEnds function (line 718 g.raphael.js), the rounding it does, while fine in some cases, is adding or subtracting years from/to the date in other cases.
Haven't stepped all the way through after this point, but since the datapoints are misplaced every time the rounding gets crazy and not when it doesn't, I'm going to go ahead and assume this is causing issues with calculating the chart columns, also before being sent to snapEnds the values are spot on just to confirm its not just receiving miscalculated data.
code of that function from g.raphael.js
snapEnds: function(from, to, steps) {
var f = from,
t = to;
if (f == t) {
return {from: f, to: t, power: 0};
}
function round(a) {
return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
}
var d = (t - f) / steps,
r = ~~(d),
R = r,
i = 0;
if (r) {
while (R) {
i--;
R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
}
i ++;
} else {
if(d == 0 || !isFinite(d)) {
i = 1;
} else {
while (!r) {
i = i || 1;
r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
i++;
}
}
i && i--;
}
t = round(to * Math.pow(10, i)) / Math.pow(10, i);
if (t < to) {
t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
}
f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
return { from: f, to: t, power: i };
},
removed the rounding nonsense from snapEnds and no more issues, not noticed any downside from either axis or any other area of the chart. If you see one I'd love to hear it though.
code of that function from g.raphael.js now:
snapEnds: function(from, to, steps) {
return {from: from, to: to, power: 0};
},
Hi if you comment this:
if (valuesy[i].length > width - 2 * gutter) {
valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
len = width - 2 * gutter;
}
if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
}
in g.line.js, It seems to solve the problem, and it also solves a similar problem with the values in the y axis.
Upgrading from v0.50 to v0.51 fixed the issue for me.
Still not sure why it occurs, adding in a transparent set was not a desirable option.
The simplest way to check for if the datapoints were rendered outside of the graph seems to be getting a bounding box for the axis set and a bounding box for the datapoints and checking the difference between the x and x2 values.
If anyone can help me with scaling the datapoint set, or figure out how to make this not happen at all, I will still happily appreciate/up vote answers
//assuming datapoints is the Raphael Set for the datapoints, axes is the
//Raphael Set for the axis, and datalines is the Raphael Set for the
//datapoint lines
var pointsBBox = datapoints.getBBox();
var axesBBox = axes.getBBox();
var xGapLeft = Math.ceil(axesBBox.x - pointsBBox.x);
//rounding up to integer to simplify, and the extra boost from y-axis doesn't
//hurt, <1 is a negligible distance in transform
var xGapRight = Math.ceil(axesBBox.x2 - pointsBBox.x2);
var xGap = 0;
if(xGapLeft > 0){
datapoints.transform('t' +xGapLeft +',0');
datalines.transform('t' +xGapLeft +',0');
xGap = xGapLeft;
}else if (xGapRight < 0) { //using else if because if it is a scale issue it will
//be too far right & too far left, meaning both are true and using transform will
//just shift it right then left and you are worse off than before, using
//set.transform(scale) works great on dataline but when using on datapoints scales
// symbol radius not placement
if (xGapLeft < 0 && xGapRight < xGapLeft) { xGapRight = xGapLeft; }
//in this case the initial point is right of y-axis, the end point is right of
//x-axis termination, and the difference between last point/axis is greater than
//difference between first point/axis
datapoints.transform('t' +xGapRight +',0');
datalines.transform('t' +xGapRight +',0');
xGap = xGapRight;
}
rehookHoverOverEvent(xGap); //there are so many ways to do this just leaving it
//here as a call to do so, if you don't the hover tags will be over the original
//datapoints instead of the new location, at least they were in my case.

Categories