Gnuplot canvas: tics disappear on zoom - javascript

I have been working for a while on a web interface related to my work. I have some data that I fit with a model iteratively. In every iteration I create a Gnuplot figure that goes online to help me keep track of the process. It works very well, however, I have some minor problems related to the canvas terminal that I couldn't fix for a long time. I use Gnuplot 4.6.5 and several browsers. They all behave consistently.
Here is an example of such a page, with two figures:
http://stelweb.asu.cas.cz/~nemeth/xtgrid_log/xtgrid_008_r1458384bfCsdd_dat.html
I have a struggle with the x-axis tics and resolution, and I think these problems may be related:
1 - x-axis coordinate below the figure does not show decimals. I think the x coordinate is truncated by Gnuplot.
2 - Axis tics disappear when we zoom in.
3 - I have some labels defined with: "set label at x,screen 0.2 '...some label...' "
These labels disappear when zooming in, even though they are set to the screen y coordinate (0.2).
4 - Zoom resets in figure 1 when mouse is moved over to figure 2 and back to 1 again. This might be a html/java issue.
A Gnuplot script to generate these figures looks like this:
set encoding iso_8859_1
set terminal canvas solid butt size 1024,410 name figure1 mousing lw 0.7
set output "figure1.js"
set samples 50, 50
set size ratio 0.4
set xtics autofreq 200
set mxtics 4
set xr [4000:5000]
set ylab "Relative flux"
set xlab "Wavelength (\305)"
set format y "%1.1e"
load 'lines.gnp' # just a bunch of labels
plot " ... some file ... "
I wonder if there is a general fix to these inconveniences.
To problem #4:
The Gnuplot generated javascript starts like this:
function ALue310128bfCsdd_dat_7_0()
{
canvas = document.getElementById("ALue310128bfCsdd_dat_7_0");
ctx = canvas.getContext("2d");
// Reinitialize mouse tracking and zoom for this particular plot
if ((typeof(gnuplot.active_plot) == "undefined" || gnuplot.active_plot != ALue310128bfCsdd_dat_7_0) && typeof(gnuplot.mouse_update) != "undefined")
{
gnuplot.active_plot_name = "ALue310128bfCsdd_dat_7_0";
gnuplot.active_plot = ALue310128bfCsdd_dat_7_0;
canvas.onmousemove = gnuplot.mouse_update;
canvas.onmouseup = gnuplot.zoom_in;
canvas.onmousedown = gnuplot.saveclick;
canvas.onkeypress = gnuplot.do_hotkey;
if (canvas.attachEvent) {canvas.attachEvent('mouseover', ALue310128bfCsdd_dat_7_0);
}
else if (canvas.addEventListener)
{canvas.addEventListener('mouseover', ALue310128bfCsdd_dat_7_0, false);}
gnuplot.zoomed = false;
gnuplot.zoom_axis_width = 0;
gnuplot.zoom_in_progress = false;
gnuplot.polar_mode = false;
ctx.clearRect(0,0,1280,513);
}
The gnuplot.zoomed and gnuplot.zoom_in_progress variables seem like they have something to do with the reinitialization of the zoom level.

If you mean the coordinates of the mousebox: For e.g. wxt the format of these mouse coordinates is adaptable with set mouse format. That doesn't work for the canvas terminal, might be a bug.
After gnuplot created the image the axis tics become only lines, i.e. the axis tics aren't regenerated when zooming. Try e.g. to zoom in around a tic: this will become very large.
Same as 2.: The screen coordinates are recognized as such only within gnuplot. After being written to the file there are only absolute coordinates. If the label coordinates fall into the zoom area, the respective label is drawn, otherwise not.
This might be a bug and can also be observed on e.g. http://gnuplot.sourceforge.net/demo_canvas_5.0/finance.html
I think 1. and 4. could be bugs, 2. and 3. are limits of the interactivity currently provided, and probably also limitations of gnuplot as such to produce such images.
Maybe its worth for you testing gnuplot-JS (demo at http://gnuplot.respawned.com/). I haven't tried it, yet.

Related

vis.js network - is there a setting to make the node labels stay the same size on zoom?

Basically the title. The client is complaining that when he zooms in, the text labels for the nodes are quite large. Is there a way to keep the node labels at a fixed font size even when zooming in or out?
From the nodes documentation (http://visjs.org/docs/network/nodes.html), there's a scaling.label option, but it doesn't seem to work. I think this is only relevant if I'm using values to scale the nodes.
Here is my implementation:
network.on( "zoom", function(properties){
var options = {
nodes: {
// 1/scale to make text larger as scale is smaller
// 16 is my default font size
font: {
size: ( 1 / network.getScale() ) * 16
}
}
};
network.setOptions(options);
});
As far as I know, there's no such option. The scaling.label option, if I understand correctly what you mean, is used to set a scaling factor, not disable zooming.
However, you can implement this yourself, namely change scaling of labels on zoom. Fortunately, there's zoom event: set a handler like
network.on('zoom',rescaleLabels);
and implement rescaleLabels by setting the corresponding scale factor to their labels. In there, you can use network.getScale() to get new scale and then set scaling of nodes.

CreateJS - scaling the canvas does not scale the mouse coordinates

I am working on a big project where exercises in Canvas are created through JSON-data and CreateJS. The purpose of having it in HTML 5 is to not have to use a separate app for your phone, you can always use the website.
Everything works fine, however in mobile the Canvas is rescaled to full screen. This is done through checking the screen size, and if it's small enough to be mobile the canvas is scaled through this code:
// browser viewport size
var w = window.innerWidth;
var h = window.innerHeight;
// stage dimensions
var ow = canvasWidth;
var oh = canvasHeight;
// keep aspect ratio
var scale = Math.min(w / ow, h / oh);
stage.scaleX = scale;
stage.scaleY = scale;
// adjust canvas size
stage.canvas.width = ow * scale;
stage.canvas.height = oh * scale;
This works great for most of the exercises, like quizzes and such, where all you have to do is click on a button. However we also have some drag and drop-exercises, and an exercise where you can color a drawing. These of course rely on the mouse coordinates to work properly. The problem is, when the canvas is scaled the mouse coordinates are not. So when you drag an item or try to draw, there is an offset happening. So your drawing appears way left of your click, and when picking up a draggable object it doesn't quite follow your click correctly.
Had I made the code from the beginning I'm fairly sure how I would have recalculated the coordinates, but since they are calculated by CreateJS I don't really know how I should go about this.
This was reported as a problem by someone about a year ago here, where this solution was suggested:
I was able to work around this by adding a top-level container and attaching my Bitmaps to that and scaling it.
The whole exercise is inside a container which I have tried to scale but to no avail. I have also tried sending the scale as a parameter to the parts of the exercise created (for example the menu, background images and such) and not scale it all together, and it seems to work okay since then I can exclude the drawing layer. But since it is a large project and many different exercises and parts to be scaled it would take quite some time to implement, and I'm not sure it's a viable solution.
Is there a good and easy way to rescale the mouse coordinates along with the canvas size in CreateJS? I have found pure Javascript examples here on SO, but nothing for CreateJS in particular.
Continued searching and finally stumbled upon this, which I hadn't seen before:
EaselJS - dragging children of scaled parent. It was exactly what I was looking for. I needed to change the coordinates I drew with this:
var coords = e.target.globalToLocal(e.stageX, e.stageY);
Then I could use the coords.x and coords.y instead of directly using e.stageX and e.stageY like before.

Circular canvas corners clickable in Chrome

I have two canvases. I have made them circular using border-radius. The 2nd is positioned inside the first one (using absolute position).
I have click events on both circles. If you click on inside canvas, the color at the point of the click is loaded in the outside canvas with opacity varying from white to the picked color and finally to black. If you click on outer canvas the exact color value at that point is loaded in the text-box at the bottom
I am unable to click in red zones (as shown in figure below) of the outer canvas when using chrome. I tried z-idex, arcs but nothing is helping me. But In Firefox everything is working fine.
Note: You can drag the picker object in the outer circle. But if you leave it in red zones, you would not be able to click it again in Chrome. Clicking in green zone will get you its control again
Code in this JSFiddle
Edit
I excluded all irrelevant code to make it easy. Now there is only a container having two canvas.
Filled simply with two distinct colors. Open following fiddle link in both chrome and firefox. Click on both cirles in different zones and see difference in chrome and firefox. I want them to behave in chrome as they do in firefox
Note I will ultimately draw an image in inner canvas.
Updated Fiddle Link
-
Your problem is because canvases currently are always rectangular, even if they don't look rectangular. Border radius makes the edges except the circle transparent, but it still doesn't stop events in Chrome on the corner areas. This is why you cannot click the bottom circle in those areas
I even tried putting it inside of a container that had a border-radius instead but the click event still goes through
With that being said, you have two options. You could either change your code to only use one canvas with the same type of layout, just drawing the background circle before the other each time. Essentially you'd draw a circle, draw your black to color to white gradient, use the xor operation to combine the two into one circle, then do the same with the rainbox gradient. You must draw the background circle first because canvas paints over the old layers every time
or
You could use javascript to only detect clicks in the circular area which takes just a little bit of math (: This solution is featured in edit below
In the future, CSS Shapes may allow canvases to be non-rectangular elements to be used, I'm actually not sure, but we don't have that capability yet at least
Edit
Alright, so after going through your code a bit it seems there are some things I should cover before I offer a solution
Setup all your finite variables outside of the functions that run every time. This means you don't put them (like radiuses, offsets, etc.) in the click function or something that runs often since they don't change
Your "radius"es are actually "diameter"s. The format of .rect goes .rect(x, y, width (diameter of circle), height (diameter of circle))
Almost always when overlaying canvases like you are you want to make them equal dimensions and starting position to prevent calculation error. In the end it makes it easier, doing all relative positioning with javascript instead of mixing it with CSS. In this case, however, since you're using border-radius instead of arc to make a circle, keep it like it is but position it using javascript ....
jQuery isn't needed for something this simple. If you're worried about any load speed I'd recommend doing it in vanilla javascript, essentially just changing the .click() functions into .onclick functions, but I left jQuery for now
You can declare multiple variables in a row without declaring var each time by using the following format:
var name1 = value1,
name2 = value2;
Variables with the same value you can declare like so:
var name1 = name2 = sameValue;
When children have position:absolute and you want it to be positioned relative to the parent, the parent can have position:relative, position:fixed, or position:absolute. I would think you'd want position:relative in this case
When you don't declare var for a variable it becomes global (unlessed chained with a comma like above). For more on that read this question
Now, onto the solution.
After talking with a friend I realized I could sort do the math calculation a lot easier than I originally thought. We can just calculate the center of the circles and use their radiuses and some if statements to make sure the clicks are in the bounds.
Here's the demo
After everything is set up correctly, you can use the following to detect whether or not it's in the bounds of each
function clickHandler(e, r) {
var ex = e.pageX,
ey = e.pageY,
// Distance from click to center
l = Math.sqrt(Math.pow(cx - ex, 2) + Math.pow(cy - ey, 2));
if(l > r) { // If the distance is greater than the radius
if(r === LARGE_RADIUS) { // Outside of the large
// Do nothing
} else { // The corner area you were having a problem with
clickHandler(e, LARGE_RADIUS);
}
} else {
if(r === LARGE_RADIUS) { // Inside the large cirle
alert('Outer canvas clicked x:' + ex + ',y:' + ey);
} else { // Inside the small circle
alert('Inner canvas clicked x:' + ex + ',y:' + ey);
}
}
}
// Just call the function with the appropriate radius on click
$(img_canvas).click(function(e) { clickHandler(e, SMALL_RADIUS); });
$(wheel_canvas).click(function(e) { clickHandler(e, LARGE_RADIUS); });
Hopefully the comments above and code make enough sense, I tried to clean it up as best as I could. If you have any questions don't hesitate to ask!

Using KineticJs to update and rescale a drawing

I am having an issue trying to update a drawing that uses a number of different drawing objects. The drawing is similar to an AutoCad drawing and is measured in mm so the scale is already being calculated in order to get the drawing to fit on the stage. When this is calculated the scale is set to one.
I have the drawing objects (lines, circles, arcs, etc.) stored in an array. I am trying to update the drawing andd rescale it without clearing the stage and doing a full redraw to improve performance.
What I am trying to do is to increment the length of the drawing. The drawing has a cut point so the objects to the right of the cut point will move by the increment value and any lines that span the cut point will increase in length. This change will require an update to the scale initially calculated to get the drawing to appear on the stage.
There are 2 Fiddles that I have set up to demonstrate the problem. The first (http://jsfiddle.net/tctruckscience/3HxuP/4/) shows what I am currently doing. The problem is that drawing will scale but it will start to move away from the right hand side of the screen.
originalRectWidth = 2600;
rectWidth = rectWidth + 20;
scaleValue = originalRectWidth / rectWidth;
oldRectWidth = rect.getWidth();
newPixelsPerScaleUnit = 260 / rectWidth;
newRectScaled = rectWidth * newPixelsPerScaleUnit;
drawingGroup.setWidth(newRectScaled);
rect.setWidth(newRectScaled);
drawingGroup.scaleBy(scaleValue);
Also, if I make a lot of increments and then make a large decrement using a text box there is an issue in the redraw. It that lines are not decremented correctly. I think it is an issue with the scaling. When I resize the page, which calls a refresh of the drawing objects from the array in which they are held, the drawing proportions are correct
The second Fiddle (http://jsfiddle.net/tctruckscience/rsEyA/15) shows how I would like the drawing to behave.
oldWidth = drawingGroup.getWidth();
newWidth = drawingGroup.getWidth() + 20;
scaleValue = originalWidth / newWidth;
Is there a way to do this?

Pixel perfect 2D mouse picking with Canvas

I'm writing a 2D game in html5 using Canvas which requires mouse click and hover events to be detected. There are 3 problems with this: detections must be pixel-perfect, objects are not rectangular (houses, weird-shaped UI buttons...), and it is required to be fast and responsive. (Obviously brute force is not an option)
So what I want to ask is how do I find out which object the mouse is on, and what are the possible optimizations.
P.S: I did some investigation and found a guy who used QuadTree here.
I have a (dated) tutorial that explains the concept of a ghost canvas which is decent for pixel-perfect hit detection. The tutorial is here. Ignore the warning about a newer tutorial, the newer one does not use the ghost canvas concept.
The idea is to draw the image in question to an in-memory canvas and then use getImageData to get the single pixel of the mouse click. Then you see if that single pixel is fully transparent or not.
If its not fully transparent, well, you've got your target.
If it is fully transparent, draw the next object to the in-memory canvas and repeat.
You only have to clear the in-memory canvas at the end.
getImageData is slow but it is your only option if you want pixel-perfect hit detection and aren't pre-computing anything.
Alternatively you could precompute a path or else an array of pixels with an offset. This would be a lot of work but might be faster. For instance if you have a 40x20 image with some transparency you'd compute an array[40][20] that would have true or false corresponding to transparent or not. Then you'd test that against the mouse position, with some offset, if the image is drawn at (25, 55) you'd want to subtract that from the mouse position and then test if the new position is true when you look at array[posx][posy].
That's my answer to your question. My Suggestion? Forget pixel-perfect detection if this is a game.
Seriously.
Instead make paths (not in canvas, in plain javascript code) that represent the objects but are not pixel perfect, for instance a house might be a square with a triangle on the top that is a very close approximation of the image but is used in its stead when it comes to hit testing. It is comparatively extremely fast to compute if a point is inside a path than it is to do pixel-perfect detection. Look up point in polygon winding number rule detection. That's your best bet, honestly.
The common solution in traditional game development is to build a click mask. You can re-render everything onto a separate off-screen canvas in a solid color (the rendering should be very quick). When you want to figure out what was clicked on, you simply sample the color at the x/y co-ordinate on the off-screen canvas. You end up building a color-->obj hash, akin to:
var map = {
'#000000' : obj1
, '#000001' : obj2
, ...
};
You can also optimize the rendering to the secondary canvas to only happen when the user clicks on something. And using various techniques, you can further optimize it to only draw the part of the canvas that the user has clicked on (for example, you can split you canvas into an NxN grid, e.g. a grid of 20x20 pixel squares, and flag all of the objects in that square -- you'd then only need to re-draw a small number of objects)
HTML5 Canvas is just a drawing plane, where you can set different transforms before calling each drawing API function. Objects cannot be created and there is no display list. So you have to build these features yourself or you can use different libraries available for this.
http://www.kineticjs.com/
http://easeljs.com/
A few months before I got interested in this and even wrote a library for this purpose. You can see it here : http://exsprite.com. Ended up facing a lot of performance issues, but because of lack of time I couldn't optimize it. It was really interesting, so waiting for some time to make it perfect.
I believe the comments should suffice. This is how I determine user intention in my 2d isometric scroller, currently located at http://untitled.servegame.com
var lastUp = 0;
function mouseUp(){
mousedown = false; //one of my program globals.
var timeNow = new Date().getTime();
if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
lastUp = new Date().getTime();
var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html
imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide.
imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);
hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.
var img = new Image();
img.src = url;
img.onload = function(){
//draw this elements image to the canvas at 0,0
hiddenCanvas.drawImage(img,0,0);
///This computes where the mouse is clicking the element.
var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
var top = $(elem).css('top').split('p')[0];
offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
offY = top - offsetTop;
offX = mouseX - offX; //apply the difference of the click point's x and y
offY = mouseY - offY;
var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
var opacity = imgPixel.data[3]; //get the opacity value of this pixel.
if(opacity == 0){//if that pixel is fully transparent
$(elem).hide();
var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
$(elem).show();
elem = temp;
}
//draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
hiddenCanvas.beginPath();
hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true);
hiddenCanvas.closePath();
hiddenCanvas.fill();
$(elem).css("top", "+=1"); //apply something to the final element.
}
}
}
In conjunction with this:
<canvas id="hiddenCanvas" width="88" height="200"></canvas>
Set the CSS positioning absolute and x = -(width) to hide;

Categories