I have created a webpage with multiple charts using D3 and Object Oriented Programming.
Working code link--> https://codepen.io/mohan-ys/pen/LYLwqrK
The problem I am facing is that the tooltip is not at the mouse location, it is somewhere else.
I tried using d3.event.pageX & d3.event.pageY instead of vis.mouse[0] & vis.mouse[1] which is in the code above but it does not work.
I am getting the tooltip as shown. When the mouse is at the right end of the graph, the tool tip moves further right, it gets closer somewhere in the middle & it goes to the other side by the time the cursor is on the left end of the chart!
The page is resized, then it is a totally different behaviour!
Can anyone help get the tooltip right a the mouse pointer (top-left corner at the mouse pointer) for all graphs & even when the page is resized (the graphs scale with page resize).
The vertical line follows the mouse perfectly!, so, if there is another way of creating the tooltip instead of a div, that is also ok for me.
The underlying problem is addressed here but the answer doesn't directly solve your problem. Basically, there's a second and optional argument to d3.pointer called target such that:
If target is not specified, it defaults to the source event’s
currentTarget property, if available. If the target is an SVG element,
the event’s coordinates are transformed using the inverse of the
screen coordinate transformation matrix...
You can make use of this argument per below noting that it will break your vertical tracking line if you try and just update vis.mouse:
// mouse moving over canvas
vis.mouse = d3.pointer(event); // keep this for the vertical tracking line
vis.mouse2 = d3.pointer(event, d3.select(vis.chartLocation)); // <--- NEW VARIABLE!
Now vis.mouse2 has a relative x and y - so use them where you set the style of the div:
d3
.select(vis.chartLocation)
.selectAll("#tooltip")
.html((d, i) => {
vis.xDate = d.values[vis.idx - 1].date;
return vis.xDate.toLocaleDateString("pt-PT");
})
.style("display", "block")
.style("left", vis.mouse2[0] + "px") // use vis.mouse2
.style("top", vis.mouse2[1] + "px") // use vis.mouse2
The clue is in that your first selection is vis.chartLocation.
Related
I recently had a developer back out on my project at the 95% completion mark and have been trying to complete this project on my own. I am currently working on the tooltip placement on the map found on this page:
https://www.shiftins.com/homeowners-insurance-california/
You can see the source here:
https://www.shiftins.com/county/index.html
There are 2 issues I need help resolving.
The tooltip for the bar charts should be located at the mouse pointer; however, it is located way right of the charts
The tooltip for the map portion seems to disappear on the bottom right half of the counties when scaled down.
I have spent quite a few hours with the trial-and-error method trying multiple solutions but I can't seem to figure it out (especially since I am not a developer).
For the map tooltip to appear ,
instead of padding-left for bar char ,give left property;.
This is because ,now the bar chart is overlapping on map chart,If you keep a border you can see the overlapping clearly.
<div id="linechart" class="col-sm-3" style="width: 60%;padding-left:120px;">
change this line to
<div id="linechart" class="col-sm-3" style="width: 60%;left:125px;">
For the bar tool tip to appear,
Now d3.event.pageX gives the x coordinate value of mouse.To get the accurate x value,you need to minus the offsetLeft value
var x = d3.event.pageX + 5-document.getElementById("linechart").offsetLeft;
You need to position the left and top i.e. x and y axis of the tooltip on mouseover/hover event. Something like this:
Add this style to the tooltip div:
"left", (d3.event.pageX) + "px").style("top", (d3.event.pageY - 28) + "px"
Here is a working example
How could I implement such mouseover effect that whenever mouse is over the linechart it shows every lines Y-value in an tooltip on hovered X?
So, in the end by moving mouse over the chart it should always show a tooltip that is updated constantly with Y value based on changed X? Now it shows tooltip only on X-scales steps e.g. 2010,2011,2012,2013,2014...
I don't have time at this very moment to write a complete solution, but I can try to point you in the right direction in terms of the part you will need that is directly related to the Google Charts API.
There Is Not A Simple Solution
First off, I'd like to make it very clear that there is not, to my knowledge, a simply solution built into the Google Charts API for this. Anything you right for this will involve rendering your own tooltip element, positioning it to the mouse location, and filling the tooltip with data yourself.
A JavaScript Framework of your choice will probably help a lot. Most have plugins or modules to handle mouseover and mouse position detection, though I can't recommend any specifically because I haven't tried this.
Chart Layout Interface
What you need to get the data values belonging to the mouse location is the Chart Layout Interface. You can get this as follows:
// create a line chart and set up a variable to store your interface
// on outside the scope of your ready handler, so you can use the
// interface in your mouse event code.
var layoutInterface;
var chart = new google.visualization.LineChart(document.getElementById('container-id'));
// set up a handler for the chart's ready event
// the chart layout interface is not available until the chart has
// been drawn
var readyHandler = function(){
layoutInterface = chart.getChartLayoutInterface();
};
// register the event handler
google.visualization.events.addListener(chart, 'ready', readyHandler);
I learned this from the demo here.
You will be using the getHAxisValue(xCoordinate) and getVAxisValue(yCoordinate) methods on the layout interface to get the data values corresponding to the x and y coordinates of the chart. The coordinates are relative to the chart's container element. See the Line Chart Documentation for information on methods available on the layout interface.
Mouse Event Handling
The mouse handling part of this is beyond the scope of my knowledge, but I do know that it is possible. I think you need to register a mouse enter event handler on your chart's container element, which would then register a mouse move, and mouse exit on the same element. The mouse exit would set display:none on your tooltip element and de-register the mouse move handler. The mouse move handler would set the absolute position of your tooltip element to the mouse location, and set it's content to the values retrieved from the chart layout interface.
Good Luck!
Aim:
I have a website with some content and svg scheme in the middle of it. When one points to the elements of the scheme, tooltips should appear next to the mouse cursor.
Problems: Based on examples like this (which was shown by Julian Berger in How to get the position of SVG element), I made working SVG. Unfortunately it is working only as long as the SVG scheme is not included into the website. Content other then SVG make evt.clientX and Y coordinates system to fail --> the tooltip starts to appear in some distance from the cursor (it seems that the more of other then SVG content I have, the further tooltip is moved away from cursor). The simple example is shown here, simply by adding couple of <br/> before the actual SVG begins.
And my question:
Do you have some ideas how to fix the position of the tooltip, so that it would appear always next to the moving cursor?
All the best,
Wojtek
All you have to do is alter mousemove handler a little. It should position the tooltip relative to the top left of the SVG, rather than the page. You do that by subtracting the position of the SVG, which we get by surrounding the SVG with a <div> element and accessing its offsetLeft and offsetTop properties.
<div id="mysvg">
<svg>...</svg>
</div>
function ShowTooltip(evt, mouseovertext) {
svg = document.getElementById("mysvg");
tooltip.setAttributeNS(null,"x",evt.clientX+11 - svg.offsetLeft);
tooltip.setAttributeNS(null,"y",evt.clientY+27 - svg.offsetTop);
...
}
Full demo here
I am trying to modify the directed graph editor in the following manner:
When a link is drawn, it won't re-position the source and target nodes. Instead the link will be drawn between the current location of the nodes.
When dragging an existing node around (using ctrl), it won't affect the position of any attached nodes attached to it. Instead all other nodes will remain in their position, and only the links attached to the dragged node will change their length according to the dragging-around.
I tried supplying this function to force's linkDistance:
force.linkDistance(function(link) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return dist;
})
Thinking that this would lead force to assume that there is no need for re-positioning the nodes at each side of the link (following the documentation).
However, this led to a run-time error, which I couldn't resolve.
Any ideas on how this behavior of the graph can be achieved?
Sounds like you want to use the fixed options, by setting it as a property of each node node, eg, { id:123, fixed:true }.
Here's a modified version
Fixed nodes don't get moved around by the force layout at all, so unless you explicitly give them an initial position, they just get assigned a random one by the layout. Note too that with all the nodes being fixed, there's no real reason to use a force directed layout.
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!