anseki / leader-line

Draw a leader line in your web page.
http://anseki.github.io/leader-line/
MIT License
3k stars 421 forks source link

Viewport #13

Closed Martin-N closed 6 years ago

Martin-N commented 6 years ago

There is no option to specify where to append the line SVG. It would be good to be able to specify a specific element / viewport to append the SVG.

This would fix the issue if you draw the lines inside a scrollable element. Currently if it is inside a scrollable element we have to run the position method while scrolling which isn't very performant and the line is drawn on top which makes it able to go outside the element instead of disappearing underneath while it is scrolled.

anseki commented 6 years ago

Hi @Martin-N, thank you for the comment.

Sorry, my English is poor. Do you mean that you want to append a leader line to a scrollable element to make it follow scrolling that element?

Martin-N commented 6 years ago

Hi @anseki

Yes, and so that the line is not visible outside it's container. See attached pic. The line should not be visible outside the orange block.

If I move the SVG element inside this container manually and give it a z-index, then it displays correctly as in it doesn't appear outside it's container and it scrolls with the content, but then it's position is off.

line

anseki commented 6 years ago

I see. To allow definite lines drawing and positioning accurately, those should be positioned based on document. And those should avoid being affected by other elements or something. The option that changes the structure makes many problems. I think that is special case that a part of the line should be hidden. So, use a mask or move the SVG element as you said, in your case. Also, use position method to follow to scrolling.

anseki commented 6 years ago

No reply came, and I close this issue.

sarnold04 commented 6 years ago

Hi @anseki,

I'm currently working on a project where this feature could be very useful. Do you have any idea about modifying your code so that a div could be specified as base document where the whole svg stuff is added?

anseki commented 6 years ago

Hi @sarnold04, thank you for the comment. SVGs are now added to the document but you can move those. Note that the moving is not supported because those should avoid being affected by other elements or something.

sarnold04 commented 6 years ago

Hi @anseki,

thanks for your quick response. I will move them after the lines have been added.

anseki commented 6 years ago

:smile:

b4nt0 commented 5 years ago

Hi @anseki, in order for us to move the element manually, is it possible that you please expose an accessor to insProps[].svg? Then we could move the element cleanly, without having to search for it in the DOM.

anseki commented 5 years ago

Hi @b4nt0, thank you for the comment. The moving the element is not supported because that should avoid being affected by other elements or something. Therefore you can move the element by yourself (i.e. hacky method) only if you understand those and the structure of the DOM.

b4nt0 commented 5 years ago

Hi @anseki, thank you for the explanation! I think the "hacky method" will not work well, since there's no reliable way to recognize the SVG. In order to avoid positioning and overlap issues, another method could probably work, such as clipping. This needs exposure of the SVG too though. What are you thoughts on this?

anseki commented 5 years ago

@b4nt0, Sorry, my English is poor. Do you mean that the querySelector method could not get the SVG element? Could you explain about your code? Also, what do you want to do with the element? e.g. masking

b4nt0 commented 5 years ago

@anseki your English is excellent :) What I mean is that the element that you create does not have any distinctive attribute that I can filter on, e.g. no ID. The only place where you store a link is in insProp list, which is internal. Therefore reliably finding the svg element related to a certain LeaderLine seems impossible.

Regarding my code to handle it - the code does not exist yet. I thought of clipping.

anseki commented 5 years ago

The querySelector method can get a certain SVG element that was added last time. However, that is hacky method as I said. The easy way that is not hacky method to clip the line is the adding another window in current document (i.e. iframe). Since LeaderLine supports multiple windows, you can clip the line by adding the line to separated iframe.

frumbert commented 4 years ago

The svg elements are attached to the dom root node (e.g body), but in this example there is a scrolling element that isn't the body - it's a div with overflow deeper in the dom tree. Scrolling this pane doesn't reflect the position of the svg elements, as noted in this issue and others.

connection-scrolling

What is your recommended course of action for this problem, if any?

anseki commented 4 years ago

Hi @frumbert, thank you for the comment. This example may help you. https://github.com/anseki/leader-line/issues/54#issuecomment-500712640

frumbert commented 4 years ago

I've taken steps to move the node into the scrolling element so that the overflow will crop/hide the svg lines as required. I've then used the negative transform you suggest (or negative margin which also works though less performant via repaints) to reposition the elements.

// 'scrollable' is my overflowing element; scrollableBbox is its bounding client rect
Array.from(document.querySelectorAll(".leader-line")).forEach(function(elem) {
    elem.style.transform = "translate(-" + (scrollable.scrollTop + scrollableBbox.x) + "px, -" + scrollableBbox.y + "px)";
    scrollable.appendChild(elem);
});

This is ok until you scroll at which point the setting line.position() repositions the top style property without taking into account the scrollTop; and similar for the left property.

connection-scrolltop

I'm using PlainDraggable to move the nodes.

// 'end' is the draggable node
    const draggable = new PlainDraggable(end, {
        onMove: function () { line.position(); },
        onMoveStart: function () { line.dash = { animation: true }; },
        onDragEnd: function () { line.dash = false; },
        autoScroll: end.closest(".overflow")
    });

inside onMove I have a reference to line but it does't seem to have any exposed property referring to the <svg class='line-leader' ... DOM element which the line is drawn by, which I would need in order to set the transform property again to take into account the scroll offset myself. Can I infer which <svg> element a line belongs to - or is it just nth-child?

Is there a reason the position() method doesn't (or can't be made to) accept scrollTop / scrollLeft when calculating the top and left style properties during its update?

I am currently keeping an array of the line objects and iterating obect and hoping they match the order of the svg elements I moved into the scrollable node area. This seems pretty jank and breaks more places than it works, and means I'm reapply the transform property ever time the AnimEvent/scroll event triggers, rather than just when the draggable node moves.

I could move the transform calculation back to the onMove function but then I still don't have a reference to the equivalent underlying svg DOM element, and it doesn't really work anyway, for example

// 'index' is a number representing the draggable node in the loop that is being created
        onMove: function () {
            line.position();
            const probableSvg = scrollable.querySelectorAll("svg.leader-line")[index];
            const cs = getComputedStyle(probableSvg);
            probableSvg.style.top = parseFloat(cs.top) + scrollable.scrollTop;
            probableSvg.style.left = parseFloat(cs.left) + scrollableBbox.scrollLeft;
        },
anseki commented 4 years ago

Hi @frumbert. Sorry, my English is poor. Do you mean that the example didn't help you? Could you show me your example by using e.g. https://jsfiddle.net/ ?

frumbert commented 4 years ago

Attaching the leader line to a child object and then scrolling the object doesn't take into account the scrolling offset when calling line.position().

https://jsfiddle.net/frumbert/Lhpvmw7d/10/

jsfiddle

How can I get the position() method to respect the scrolling offset of the wrapper when it recalculates?

anseki commented 4 years ago

You don't need the position() method that respects the scrolling offset because you already did that by using the wrapper. That is, you only have to fix the position of the wrapper. And also, it seems that you misunderstood autoScroll option. https://jsfiddle.net/to5b4srx/

frumbert commented 4 years ago

Yes this example is much clearer, and I can see my mistakes now. I was worried on the performace of calling getBoundingClientRect so frequently (e.g. every time onMove fires) but if I cache it or use AnimEvent the tracking position breaks - so I guess I'll have to live with it. Thanks for your assistance!

anseki commented 4 years ago

I'm glad if I could help you. :smile: If you consider the performance, you can call the wrapperPosition() only once only when that was scrolled. Foe example: https://jsfiddle.net/zqhv32a1/

frumbert commented 4 years ago

Hmm, i thought I knew what my problem was until I put leader line into my box which has scrolling. The page itself never scrolls but a box within it does, so in the wrapperPosition function I offset by the scroll position of the overflowing container. Scrolling also updates the position of the wrapper.

I made a complete fiddle using all my current [unfinished] source, rather than a quick cut down example. Code needs a cleanup. https://jsfiddle.net/frumbert/m5acL241/2/

The steps to reproduce the issue I am seeing are:

  1. Make the results window reasonably wide and short enough that it shows a scroll bar.
  2. Drag a couple of leader lines to their connectors (dots)
  3. Scroll the window down a bit - note the leader lines appear to scroll with the page
  4. drag another leader line.

The position of the last leader line is now incorrect but the other leader lines still appear in the correct positions.

Here is an animation showing what I see:

scroll-issue

anseki commented 4 years ago

Hi @frumbert, did you read my comment?

frumbert commented 4 years ago

Yes, I read your comment and your code and based my reply on it, as you can see by me adopting your code into my example. Thanks for taking the time.

Since posting my problem, I've gone back to the drawing board since leader-line obviously can't work properly when the svgs are appended somewhere other than the root - there are too many issues relating to scrolling. Perhaps you could mention in your documentation that moving the svgs from the body into an overflowing object isn't reccomended? It would save a lot of fiddling about.

I've re-engineered my example page so that there isn't a scrolling container and rather let the body element be the scrolling container - as you probably intended.

https://jsfiddle.net/frumbert/dL96gta5/3/ is effectively working as intended (nearly).

reload-issue

anseki commented 4 years ago

I think your first mistake is that you misunderstood getBoundingClientRect method. Then you miscalculated the position. Your second mistake is that you overlooked my comment that I said that I never recommend moving SVG elements because that is not supported. It is better for you to use another library that you like because it seems that you don't need LeaderLine.

Mahesh9959-mahi commented 4 years ago

Hi @anseki, When I tried to vertical scroll the Links are going out of bound, I have tried with following

var elmSvgClip = document.getElementsByClassName('leader-line'), elmClipRect = document.getElementById('leader-line-46-mask-bg-rect'), posPoint = elmSvgClip.createSVGPoint();

But i'm getting elmSvgClip.createSVGPoint() is not a function error and weird behaviour of link. I have attached the screenshot of links for your reference, Can you help how we can fix this, By the way I'm creating the links dynamically..!

LinksOverflowIssue

anseki commented 4 years ago

Hi @Mahesh9959-mahi, thank you for the comment. Try this:

console.log(elmSvgClip);

Maybe, elmSvgClip is undefined.

anseki commented 4 years ago

Ah, it is HTMLCollection maybe. Try the elmSvgClip[0] instead.

Mahesh9959-mahi commented 4 years ago

while scrolling controller is not hitting the upadate()

anseki commented 4 years ago

Unfortunately your code didn't work. It seems that the code that you indicated is a part of code. To reproduce that, could you show me an example by using e.g. https://jsfiddle.net/ ?

Mahesh9959-mahi commented 4 years ago

https://jsfiddle.net/heuj7mso/ I have same issue what ever they discribed, But in my case AnimEvent getting not defined error and controller not hitting elmLine.style.clipPath = 'url(#clip)'; line

Mahesh9959-mahi commented 4 years ago

Hi @anseki,

Is there any way to change the lines from body to specific div, It's targeting the body that's why it's overlapping headers as well

anseki commented 4 years ago

You can do that, but it seems that you already did that. This is your code:

elemWrapper.appendChild(document.querySelector('body>.leader-line:last-of-type'))

These also may help you: https://github.com/anseki/leader-line/issues/25 https://github.com/anseki/leader-line/issues/54

Mahesh9959-mahi commented 4 years ago

Why AnimEvent is giving undefined, I have added as well

anseki commented 4 years ago

To reproduce that, could you show me an example by using e.g. https://jsfiddle.net/ ?

whxleemdddd commented 3 years ago

let rect_l=$('.note_page_div').offset().left; let rect_t=$('.note_page_div').offset().top; let rect_b=($('.note_page_div').offset().top+$('.note_page_div').height()+$('.note_page_div').scrollTop())+'px'; let rect_r=($('.note_page_div').offset().left+$('.note_page_div').width()+$('.note_page_div').scrollLeft())+'px'; for(let line of lines){ line.position(); } $('.leader-line').each(function(){ let svg_t=(rect_t-$(this).offset().top)+'px'; let svg_l=(rect_l-$(this).offset().left)+'px'; $(this)[0].style.clip='rect('+svg_t+','+rect_r+','+rect_b+','+svg_l+')' }) $('.leader-line-areaAnchor').each(function(){ let svg_t=(rect_t-$(this).offset().top)+'px'; let svg_l=(rect_l-$(this).offset().left)+'px'; $(this)[0].style.clip='rect('+svg_t+','+rect_r+','+rect_b+','+svg_l+')' })

anseki commented 3 years ago

Hi @whxleemdddd, thank you for the comment. What is that?

AhmedAyachi commented 2 years ago

I created a leaderline class extension please check this https://github.com/AhmedAyachi/LeaderLine

anseki commented 2 years ago

Hi @AhmedAyachi, thank you for the comment. Unfortunately, many tests failed, However, your special library might be useful for your app. :smile: