I was wandering if there is a way to adjust width of the math mathjax renders. Some math expression I have are longer and won't fit in a box I have created. Is there a way to squeeze it and make it fit maybe by changing the size or width? I have tried using line breaks but that isn't what I want. An example would be a mathjax like this:
2x+3+4 - /intcos(x) dx
234567897+sin(2x)+34567890987654.
Displaying the last line would be a problem because it won't fit in the box. It overflows
Well, you could use \small or \scriptsize or \Tiny (non-standard) or \tiny within the mathematics to make it appear in a smaller size.
Alternatively, you could put a <span style="font-size:70%">...</span> around the mathematics to get the math to be scaled to whatever size you need. E.g.,
<span style="font-size:70%">\(234567897+sin(2x)+34567890987654\)</span>
Note that the math delimiters must be inside the <span>.
I found a solution that doesn't require adding elements or css code:
// resize all LaTeX Display elements to they fit in on screen
function cvonk_ResizeMathJax() {
jQuery('.MathJax_Display').each(function(ii, obj) {
var latex = obj.children[0];
var w = latex.offsetWidth;
var h = latex.offsetHeight;
var W = obj.offsetWidth;
if (w > W) {
obj.style.fontSize = 95 * W / w + "%";
}
});
}
window.MathJax = {
AuthorInit: function() {
MathJax.Hub.Register.StartupHook("Begin", function() {
MathJax.Hub.Queue(function() {
cvonk_ResizeMathJax();
});
});
},
jax: ["input/TeX", "output/HTML-CSS", "output/NativeMML"],
extensions: ["tex2jax.js"]
};
window.addEventListener("resize", function() {
cvonk_ResizeMathJax();
});
From the Google Groups discussion linked to above:
function changeSize(button) {
var myeqn = document.getElementById('myeqn');
myeqn.style.fontSize = button.textContent;
MathJax.Hub.Queue(
['Rerender', MathJax.Hub, 'myeqn'],
function () {
document.getElementById('mylabel').innerHTML =
'width: ' + myeqn.offsetWidth + ", height: " + myeqn.offsetHeight;
});
}
See http://jsfiddle.net/nLyraL1f/ or http://jsfiddle.net/s2bjepk6/.
This is also nice because it gets the width and height of the rendered latex, useful for things like rendering it as an element positioned over a canvas since you can draw things on the canvas around it.
Related
Please look at
https://stackblitz.com/edit/js-wxidql
Here is my code.
import { fabric } from "fabric";
const canvas = new fabric.Canvas("c", { isDrawingMode: true });
canvas.setBackgroundColor("rgb(255,73,64)", canvas.renderAll.bind(canvas));
canvas.on("path:created", e => {
let mousePath = e.path;
let offsetPath = new fabric.Path();
let offsetLeft = mousePath.left + 60;
let offsetTop = mousePath.top + 60;
offsetPath.setOptions({
path: mousePath.path,
left: offsetLeft,
top: offsetTop,
width: mousePath.width,
height: mousePath.height,
fill: '',
stroke: 'black'
});
canvas.add(offsetPath);
canvas.requestRenderAll();
});
Here is an resulting image of my drawing session.
I drew the happy face in the top left corner of the canvas with my mouse.
The offset image was added by my code.
How can I change my code to make the offset drawing look like the one drawn with my mouse?
Edit: from https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords
I tried using
offsetDraw.setCoords();
but I was unable to find a way to make it work.
Edit: What I have presented here is a minimized example. I am working on an larger project where I save each path drawn by the user. Later I recreate those paths in an animation like sequence.
Edit: I made some changes to my code in a effort to understand fabricjs.
const canvas = new fabric.Canvas("c", { isDrawingMode: true });
canvas.setBackgroundColor("rgb(255,73,64)", canvas.renderAll.bind(canvas));
canvas.on("path:created", e => {
let mousePath = e.path;
let offsetPath = new fabric.Path();
let offsetLeft = mousePath.left + 60;
let offsetTop = mousePath.top + 60;
offsetPath.setOptions({
path: mousePath.path,
//left: offsetLeft,
//top: offsetTop,
width: mousePath.width,
height: mousePath.height,
fill: '',
stroke: 'black'
});
canvas.add(offsetPath);
canvas.requestRenderAll();
console.log("mousePath left " + mousePath.left + " top " + mousePath.top);
console.log("offsetPath left " + offsetPath.left + " top " + offsetPath.top);
});
In that code, I commented out the setting of the left and top properties of the offsetPath and added console.log lines. I drew a circle in the top left corner of the canvas with my mouse. The resulting image was the following.
The following was printed in the console.
mousePath left 7.488148148148148 top 10.5
offsetPath left -0.5 top -0.5
I don't understand the results. Why was the offset circle rendered in that position?
Edit: I drew another test with my mouse.
It seems that the code repeats the paths of concentric circles rather well. But, the vertical lines are moved out of their correct position. Their displacements increase as the distance from the center increases. Why?
I found a solution for my own question. But, if someone has a better one, then please post it. Look at
https://stackblitz.com/edit/js-ncxvpg
The following is the code.
const canvas = new fabric.Canvas("c", { isDrawingMode: true });
canvas.setBackgroundColor("rgb(255,73,64)", canvas.renderAll.bind(canvas));
canvas.on("path:created", e => {
let mousePath = e.path;
mousePath.clone(clone => {
clone.setOptions({
left: mousePath.left + 100,
top: mousePath.top + 100
});
canvas.add(clone);
});
});
If someone has an explaination as to why my original code didn't work, then please post it.
thanks for sharing your comments on my question. I am facing a similar issue, just that if I click anywhere on the Canvas the first time. Then the offset issue vanishes. This happens for any object that I programmatically insert into the canvas.
In my case, I suspect that the canvas.pointer is having some offset of the current mouse position. However, when I click and add one very small path of 1pixel, then the pointer offset gets corrected. After this, all subsequent paths or objects created programatically are shown at the correct positions.
I want to use image-maps inside fancybox 3. Goal is to display mountain panoramas, where the user could point on a summit and get name and data. The usual recommendation is to use a SVG based image map for this like in this pen. Due to the size of the images the fancybox zoom functionality is important.
While fancybox will display SVGs as an image like in this pen it is not possible to use the <image> tag with an external source inside the SVG file. Even worse: SVG files used as source of an <img> tag would not show the map functionality (see this question).
I tried to replace the <img> tag in fancybox with an <object> tag using the SVG file as data attribute. This shows the image with the map functionality correctly but fancybox won't zoom it any more.
So eventually the question boils down to how I can make an <object> (or an inline SVG or an iframe) zoomable just like an image in fancybox 3.
I'm open to other solutions as well. I only want to use fancybox to keep the appearance and usage the same as other image galleries on the same page. I'd even use an old style <map>, where I would change the coords using jquery to have it responsive. I tried that, attaching the map manually in developer tools as well as programmatically in afterLoad event handler, but apparently this doesn't work in fancybox 3 either.
The areas are polygons, so using positioned div's as overlays is no solution.
Edit: I just discovered that I can replace <img> with a <canvas> in fancybox without loosing the zoom and drag functionality. So in theory it would be possible to use canvas paths and isPointInPath() methode. Unfortunately I need more than one path, which requires the Path2D object, which is not available in IE...
Since all options discussed in the question turned out to be not feasible and I found the pnpoly point in polygon algorithm, I did the whole thing on my own. I put the coordinates as percentages (in order to be size-independent) in an array of javascript objects like so:
var maps = {
alpen : [
{type:'poly',name:'Finsteraarhorn (4274m)',vertx:[56.48,56.08,56.06,56.46], verty:[28.5,28.75,40.25,40.25]},
{type:'rect',name:'Fiescherhörner (4049m)',coords:[58.08,29.5,59.26,43.5]},
{type:'poly',name:'Eiger (3970m)',vertx:[61.95,61.31,61.31,60.5,60.5], verty:[43,35.25,30.25,30.25,45.5]}
]
}; // maps
Since the pnpoly function requires the vertices for x and y separately I provide the coordinates this way already.
The Id of the map is stored in a data attribute in the source link:
<a href="/img/bilder/Alpen.jpg" data-type='image' data-Id='alpen' data-fancybox="img" data-caption="<h5>panorama of the alps from the black forest Belchen at sunset</h5>">
<img src="/_pano/bilder/Alpen.jpg">
</a>
CSS for the tooltip:
.my-tooltip {
color: #ccc;
background: rgba(30,30,30,.6);
position: absolute;
padding: 5px;
text-align: left;
border-radius: 5px;
font-size: 12px;
}
pnpoly and pnrect are provided as simple functions, the handling of that all is done in the afterShow event handler:
// PNPoly algorithm checkes whether point in polygon
function pnpoly(vertx, verty, testx, testy) {
var i, j, c = false;
var nvert = vertx.length;
for(i=0, j=nvert-1; i<nvert; j=i++) {
if (((verty[i] > testy) != (verty[j] > testy)) &&
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
c = !c;
}
}
return c;
}
// checks whether point in rectangle
function pnrect(coords,testx,testy) {
return ((testx >= coords[0]) && (testx <= coords[2]) && (testy >= coords[1]) && (testy <= coords[3]));
}
$("[data-fancybox]").fancybox({
afterShow: function( instance, slide ) {
var map = maps[$(slide.opts.\$orig).data('id')]; // Get map name from source link data-ID
if (map && map.length) { // if map present
$(".fancybox-image")
.after("<span class='my-tooltip' style='display: none'></span>") // append tooltip after image
.mousemove(function(event) { // create mousemove event handler
var offset = $(this).offset(); // get image offset, since mouse coords are global
var perX = ((event.pageX - offset.left)*100)/$(this).width(); // calculate mouse coords in image as percentages
var perY = ((event.pageY - offset.top)*100)/$(this).height();
var found = false;
var i;
for (i = 0; i < map.length; i++) { // loop over map entries
if (found = (map[i].type == 'poly') // depending on area type
?pnpoly(map[i].vertx, map[i].verty, perX, perY) // look whether coords are in polygon
:pnrect(map[i].coords, perX, perY)) // or coords are in rectangle
break; // if found stop looping
} // for (i = 0; i < map.length; i++)
if (found) {
$(".my-tooltip")
.css({bottom: 'calc(15px + '+ (100 - perY) + '%'}) // tooltip 15px above mouse coursor
.css((perX < 50) // depending on which side we are
?{right:'', left: perX + '%'} // tooltip left of mouse cursor
:{right: (100 - perX) + '%', left:''}) // or tooltip right of mouse cursor
.text(map[i].name) // set tooltip text
.show(); // show tooltip
} else {
$(".my-tooltip").hide(); // if nothing found: hide.
}
});
} else { // if (map && map.length) // if no map present
$(".fancybox-image").off('mousemove'); // remove event mousemove handler
$(".my-tooltip").remove(); // remove tooltip
} // else if (map && map.length)
} // function( instance, slide )
});
Things left to do: Find a solution for touch devices, f.e. provide a button to show all tooltips (probably rotated 90°).
As soon as the page is online I'll provide a link here to see it working...
i'm using the signature-pad plugin and i'm having some issues whith the resize event:
- Multiple resizes lead to a loss in quality and the signature "moves" at each resize of the browser window ending with no signature in canvas.
- In some cases, the isEmpty() function wont work and i'll be able to save the empty signature.
Optional question : how can i detect an empty signature on php side ?
Thank you :)
Below my code :
$(window).resize(function() {
resizeCanvas();
});
var wrapper1 = document.getElementById("signature-pad"),
clearButton1 = wrapper1.querySelector("[data-action=clear]"),
canvas1 = wrapper1.querySelector("canvas"),
signaturePad1;
var wrapper2 = document.getElementById("signature-pad-paraphe"),
clearButton2 = wrapper2.querySelector("[data-action=clear]"),
canvas2 = wrapper2.querySelector("canvas"),
signaturePad2;
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
signaturePad1 = new SignaturePad(canvas1);
signaturePad2 = new SignaturePad(canvas2);
function resizeCanvas() {
//Sauvegarde sig / par
var sig = signaturePad1.toDataURL();
var par = signaturePad2.toDataURL();
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas1.width = canvas1.offsetWidth * ratio;
canvas1.height = canvas1.offsetHeight * ratio;
canvas1.getContext("2d").scale(ratio, ratio);
canvas2.width = canvas2.offsetWidth * ratio;
canvas2.height = canvas2.offsetHeight * ratio;
canvas2.getContext("2d").scale(ratio, ratio);
// redraw
signaturePad1.fromDataURL(sig);
signaturePad2.fromDataURL(par);
}
window.onresize = resizeCanvas;
resizeCanvas();
// Init -> retourne la bonne valeur de isEmpty -> !!? Not sure if needed
signaturePad1.clear();
signaturePad2.clear();
var signature = $('#confirm_delete_signature').val();
if(signature){
signaturePad1.fromDataURL(signature);
}
var paraphe = $('#confirm_delete_paraphe').val();
if(paraphe){
signaturePad2.fromDataURL(paraphe);
}
clearButton1.addEventListener("click", function (event) {
signaturePad1.clear();
});
clearButton2.addEventListener("click", function (event) {
signaturePad2.clear();
});
Here is i developed a little solution;
Here are two key DOM elements:
div#id_wrapper
canvas#id
Considered it may be applied at devices with different devicePixelRatio and on screens changins theirs width (f.i.: portrait-landscape orientation).
export class FlexSignatureComponent extends React.Component {
state = {
width: 0,
lines: [],
storedValue: undefined,
validationClass: '', // toggles between 'is-invalid'/'is-valid'
validationMessage: ''
}
The lib initiation is right after the component got loaded:
componentDidMount = () => {
this.signPad = new SignaturePad(document.getElementById(this.props.htmlid), {
onEnd: this.onChangeSignaturePad,
backgroundColor: '#fff'
});
if (this.valueHolder.current.value) {
const data = JSON.parse(this.valueHolder.current.value);
this.state.lines = data.value;
this.state.width = 100;
}
//you need the next workarounds if you have other onWidnowResize handlers manarging screen width
//setTimeout-0 workaround to move windowResizeHandling at the end of v8-enging procedures queue
// otherwise omit setTimeout and envoke func as it is
setTimeout(this.handleWindowResize, 0);
window.addEventListener("resize", () => setTimeout(this.handleWindowResize, 0));
}
First handle window resize change
handleWindowResize = () => {
if (this.state.storedValue) {
const prevWrapperWidth = this.state.width;
const currentWrapperWidth = $(`#${this.props.htmlid}_wrapper`).width();
const scale = prevWrapperWidth / currentWrapperWidth;
this.state.width = currentWrapperWidth;
this.setRescaledSignature(this.state.lines, scale);
this.resetCanvasSize();
this.signPad.fromData(this.state.lines)
} else
this.resetCanvasSize()
}
Second rescaleSignature to another width
setRescaledSignature = (lines, scale) => {
lines.forEach(line => {
line.points.forEach(point => {
point.x /= scale;
point.y /= scale;
});
});
}
Finally updated canvas size
resetCanvasSize = () => {
const canvas = document.getElementById(this.props.htmlid);
canvas.style.width = '100%';
canvas.style.height = canvas.offsetWidth / 1.75 + "px";
canvas.width = canvas.offsetWidth * devicePixelRatio;
canvas.height = canvas.offsetHeight * devicePixelRatio;
canvas.getContext("2d").scale(devicePixelRatio, devicePixelRatio);
}
Here we on every change add new drawn line to this.state.lines
and prepare the lines to be submited as json.
But before the submission they need to create deepCopy and to be rescaled to conventional size (its width is equal 100px and DPR is 1)
onChangeSignaturePad = () => {
const value = this.signPad.toData();
this.state.lines = value;
const currentWrapperWidth = $(`#${this.props.htmlid}_wrapper`).width();
const scale = currentWrapperWidth / 100;
const ratio = 1 / devicePixelRatio;
const linesCopy = JSON.parse(JSON.stringify(value));
this.setRescaledSignature(linesCopy, scale, ratio);
const data = {
signature_configs: {
devicePixelRatio: 1,
wrapper_width: 100
},
value: linesCopy
};
this.state.storedValue = JSON.stringify(data);
this.validate()
}
One more thing is the red button to swipe the previous signatures
onClickClear = (e) => {
e.stopPropagation();
this.signPad.clear();
this.valueHolder.current.value = null;
this.validate()
}
render() {
let {label, htmlid} = this.props;
const {validationClass = ''} = this.state;
return (
<div className="form-group fs_form-signature">
<label>{Label}</label>
<div className="fs_wr-signature">
<button className={'fs_btn-clear'} onClick={this.onClickClear}>
<i className="fas fa-times"></i>
</button>
<div id={htmlid + '_wrapper'} className={`w-100 fs_form-control ${validationClass}`}>
<canvas id={htmlid}/>
</div>
</div>
<div className={' invalid-feedback fs_show-feedback ' + validationClass}>Signature is a mandatory field</div>
</div>
)
}
postWillUnmount() {
this.signPad.off();
}
the used lib signature pad by szimek
Used React and Bootstrap and some custome styles
the result would be
You didn't provide a full example, or much explanation of the code, so it's hard to tell what all is going on here, but I'll do my best to give as full an answer as I can.
Saving
First, if I understand the docs correctly, $(window).resize will be triggered at the same time as window.onresize. You use both. That might be causing some issues, maybe even the issues with saving.
The following code is run once, and I'm not sure what it's supposed to do:
var signature = $('#confirm_delete_signature').val();
if(signature){
signaturePad1.fromDataURL(signature);
}
var paraphe = $('#confirm_delete_paraphe').val();
if(paraphe){
signaturePad2.fromDataURL(paraphe);
}
It looks like it's supposed to be deleting the signature (since the selector is #confirm_delete_signature), but it instead, it's restoring a signature from some data stored in the node as a string. That might be causing issues too.
That said, I'm not sure why saving isn't working, but I can't find the code of your saving function, so it's very hard to say. Maybe I missed something.
I'm not familiar with php, sorry.
Resizing
For resizing, I think the React version that #Alexey Nikonov made might work with React (I didn't run it). You have to scale the positions of the points of the lines along with the changing size of the canvas.
I wanted a version closer to vanilla js, so I recreated it with just signature_pad v4.1.4 and jQuery at https://jsfiddle.net/j2Lurpd5/1/ (with an improvement to ratio calculation).
The code is as follows, though it doesn't have a button to clear the canvas:
<div id="wrapper">
<canvas id="pad" width="200" height="100"></canvas>
</div>
canvas {
border: red 1px solid;
}
// Inspiration: https://stackoverflow.com/a/60057521
// Version with no React
const canvas = document.querySelector('#pad');
const signPad = new SignaturePad(canvas);
// Doesn't work without the #wrapper. Probably because #pad
// needs it to be able to be 100% of it. Not sure exactly
// why that makes a difference when #wrapper doesn't have
// a width set on it. Though #pad alone does work after the
// first resize.
let prevWidth = $('#wrapper').width();
let lines = [];
setTimeout(resizeSignatureAndCanvas, 0);
window.addEventListener("resize", () => setTimeout(resizeSignatureAndCanvas, 0));
window.addEventListener("orientationchange", () => setTimeout(resizeSignatureAndCanvas, 0));
function resizeSignatureAndCanvas () {
// Get the current canvas contents
lines = signPad.toData();
// if there are no lines drawn, don't need to scale them
if ( signPad.isEmpty() ) {
// Set initial size
resizeCanvas();
} else {
// Calculate new size
let currentWidth = $('#wrapper').width();
let scale = currentWidth / prevWidth;
prevWidth = currentWidth; // Prepare for next time
// Scale the contents along with the width
setRescaledSignature(lines, scale);
// Match canvas to window size/device change
resizeCanvas();
// Load the adjusted canvas contents
signPad.fromData(lines);
}
};
// This is really the key to keeping the contents
// inside the canvas. Getting the scale right is important.
function setRescaledSignature (lines, scale) {
lines.forEach(line => {
line.points.forEach(point => {
// Same scale to avoid warping
point.x *= scale;
point.y *= scale;
});
});
};
function resizeCanvas () {
/** Have to resize manually to keep the canvas the width of the
* window without distorting the location of the "pen". */
// I'm not completely sure of everything in here
const canvas = $('#pad')[0];
// Not sure why we need both styles and props
canvas.style.width = '100%';
canvas.style.height = (canvas.offsetWidth / 1.75) + 'px';
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
let ratio = Math.max(window.devicePixelRatio || 1, 1);
// This part causes the canvas to be cleared
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
};
As you can see from my notes, I'm not completely sure why every part works, but from what I can tell it does preserve the behavior of the version that #Alexey Nikonov made.
I need to position text inputs at various places on a KonvaJS layer. I found the following code at https://konvajs.github.io/docs/sandbox/Editable_Text.html and I'm trying to understand the textPosition, stageBox, and areaPosition vars in this code. I want my stage centered in the browser window, but when I do that, the textarea (activated on dblclick) pops up way off to the left. I can't get a console readout of the x/y coordinates, so I can't visualize how the positioning works &, thus, how to change it. Can anyone explain, or point me in the right direction?
var text_overlay = new Konva.Layer();
stage.add(text_overlay);
var textNode = new Konva.Text({
text: 'Some text here',
x: 20,
y: 50,
fontSize: 20
});
text_overlay.add(textNode);
text_overlay.draw();
textNode.on('dblclick', () => {
// create textarea over canvas with absolute position
// first we need to find its position
var textPosition = textNode.getAbsolutePosition();
var stageBox = stage.getContainer().getBoundingClientRect();
var areaPosition = {
x: textPosition.x + stageBox.left,
y: textPosition.y + stageBox.top
};
// create textarea and style it
var textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.value = textNode.text();
textarea.style.position = 'absolute';
textarea.style.top = areaPosition.y + 'px';
textarea.style.left = areaPosition.x + 'px';
textarea.style.width = textNode.width();
textarea.focus();
textarea.addEventListener('keydown', function (e) {
// hide on enter
if (e.keyCode === 13) {
textNode.text(textarea.value);
text_overlay.draw();
document.body.removeChild(textarea);
}
});
})
// add the layer to the stage
stage.add(text_overlay);
UPDATE: I solved part of the problem--the textarea showing up way out of position. You need to use 2 divs in the HTML file instead of one, like so:
<div id="containerWrapper" align="center"><div id="container"></div></div>
Thanks to Frens' answer on Draw border edges of the Konvajs container Stage in html for that one!
Scratching my head on this one.
We have a list of text on the left side of the page. Each item in the list has a data-id attribute that makes it easy to match up corresponding schools in our SVG map. This SVG map is a map of the US, and has school locations fed in from a CSV excel sheet and stored in "schools" for access.
circles.selectAll("circles")
.data(schools)
.enter().append("svg:a")
.attr("xlink:href", function(d) { return d.url; })
.append("svg:circle")
.attr("school", function(d, i) { return d.name; })
.attr("id", function(d, i) { return d.id; })
.attr("cx", function(d,i) { return d.longitude; })
.attr("cy", function(d,i) { return d.latitude; })
.attr("r", function(d,i) { return 6; })
.attr("i", function(d,i) { return i; })
.attr("class", "icon")
So when a user hovers over this list of text I previously mentioned, I use this function:
mapSearch = function(id) {
d3.selectAll("circle")
.filter(function(d) {
if (d.id == id) {
return show_bubble_2(d);
}
})
}
Which calls:
show_bubble_2 = function(school_data) {
var school_info = school_data,
latitude = school_info.latitude,
longitude = school_info.longitude;
bubble.css({
"left": (longitude - 75)+"px",
"top": (latitude - 67)+"px"
});
bubble.html("<h1>" + school_info.name + "</h1>" + "<p>" + school_info.city + ", " + school_info.state + "</p>")
.attr("class", function(d) { return school_info.letter; });
bubble.addClass("active");
}
This works unless we start resizing the map to fit different screen sizes, or unless we do special zoom functions on the map. Then the bubbles closer to the west coast are where they're supposed to be but the ones on the east coast are way off. In short, it's a complete nightmare and not at all scalable.
My question: How do I just append this DIV to the corresponding circle ID instead of using an absolute positioned DIV so that no matter what size the map is, the bubble will always pop up right on top of that circle.
I have tried appending inside the if (d.id == id) { } but it always returns errors and so far I haven't figured it out. I'll keep trying something along those lines because I feel like that's the way to do it. If you have a better solution or could point me in the right direction, I would really appreciate it.
Thanks, and have a good one!
You can find the position of the circle even if there is a transform applied by using Element.getBoundingClientRect.
You could use your filtered selection, get the .node() and find its bounding rect. Then by adjusting for the scroll position, you can find the values of top and left to give to your bubble.
This means that the position of the bubble is based on the actual position at which the circle appears on the page, rather than being based on its data, which would require you to take the transforms into account. Try something like this:
mapSearch = function(id) {
// get the selection for the single element that matches id
var c = d3.selectAll("circle")
.filter(function(d) {
if (d.id == id) {
return show_bubble_2(d);
}
});
// get the bounding rect for that selection's node (the actual circle element)
var bcr = c.node().getBoundingClientRect();
// calculate the top/left based on bcr and scroll position
var bubbleTop = bcr.top + document.body.scrollTop + 'px',
bubbleLeft = bcr.left + document.body.scrollLeft + 'px';
// set the top and left positions
bubble.css({
'top': bubbleTop,
'left': bubbleLeft
});
}
Of course, if you are zooming or panning and want the bubble to remain on the circle, you will need to recalculate these values inside your zoom and pan functions, but the process would be the same.
HERE is a demo using circles that are randomly placed within a g element that has a translation and scale applied. Click on an item in the list to place the bubble on the corresponding circle.
A <div> is HTML. A <circle> is SVG. You can't (easily) put HTML elements inside SVG. You'd have to use <foreignobject> elements to do that. (See this question for details.) Alternatively, you could use native SVG elements such as <tspan> instead of <div>