Forum Discussion

JohnCooper-be3c's avatar
JohnCooper-be3c
Community Member
24 days ago
Solved

Drawing two intersecting lines on a graph in Storyline

So I have  a graph say, with x and y-axes from zero to 100, I have two variables - let's call them x_value and y_value that I have calculated in my Storyline course.  I want to draw two intersecting lines to show the intersection point of the two lines on the graph...

,,,any neat and tidy suggestions - I have done it using JavaScript - but my code is ugly, ugly, ugly... and I can only get the lines to expand out from the centre I don't seem to be able to left anchor  or bottom anchor my lines in the method I have used.

  • Hello John,

    I don’t recommend using the Storyline lines for a few reasons. Instead, you can create a 100% transparent shape (don't use a shape with no fill! add a fill and set the transparency to 100%) and use the code below. 

    Don’t forget to change the data-model-id and execute the JavaScript when the timeline starts.

    How the code works

    Overlay injection – A full‑size overlay (.line-overlay) is appended inside the 100% transparent shape container (data-model-id="6Wm53ht8QUh").

    Dynamic lines – Two absolutely positioned <div> elements (vertical and horizontal) are placed inside the overlay. Their left and top are set with percentages, so they stay in the correct spot even when the slide scales.

    Polling loop – Every pollRateMs milliseconds the script compares Storyline variables x_value and y_value to the previous values. When either changes, the lines are redrawn and animated once (GSAP if available, CSS transition otherwise).

    How to customise

    Update frequency – Edit the pollRateMs constant (e.g., 100 for 10 updates per second).

    Line thickness – Change width (vertical line) or height (horizontal line) in the marked “thickness” style lines.

    Line colour – Adjust the background property in the marked “color” style lines.

    Animation speed / easing – Modify duration and ease in the GSAP block, or the transition shorthand in the CSS‑fallback block.

    /*  Live tracking + single animation   */
    (function () {
    
      /* EDIT → change how often the script checks for new values (ms) */
      const pollRateMs = 200;             // 5× per second
    
      /* internal refs */
      const graphSelector = '[data-model-id="6cPIlzq1zZH"]';
      const player        = GetPlayer();
      let   lastX         = player.GetVar('x_value');
      let   lastY         = player.GetVar('y_value');
    
      /* first draw */
      drawLines(lastX, lastY);
    
      /* watch Storyline variables */
      setInterval(() => {
        const x = player.GetVar('x_value');
        const y = player.GetVar('y_value');
        if (x !== lastX || y !== lastY) {
          drawLines(x, y);
          lastX = x;
          lastY = y;
        }
      }, pollRateMs);
    
      /* ---------- core ---------- */
      function drawLines(x_value, y_value) {
        const graph = document.querySelector(graphSelector);
        if (!graph) return;
    
        /* overlay that stretches 100 % of the graph */
        let overlay = graph.querySelector('.line-overlay');
        if (!overlay) {
          overlay = Object.assign(document.createElement('div'), {
            className: 'line-overlay'
          });
          Object.assign(overlay.style, {
            position: 'absolute',
            inset: '0',
            pointerEvents: 'none'
          });
          graph.appendChild(overlay);
        }
    
        /* vertical line */
        let v = overlay.querySelector('.v');
        if (!v) {
          v = Object.assign(document.createElement('div'), { className: 'v' });
          Object.assign(v.style, {
            position: 'absolute',
            top: '0',
            bottom: '0',
            /* EDIT → vertical line thickness  */
            width: '1px',
            /* EDIT → vertical line color      */
            background: '#d61f27',
            transformOrigin: 'bottom center'
          });
          overlay.appendChild(v);
        }
        v.style.left = x_value + '%';
    
        /* horizontal line */
        let h = overlay.querySelector('.h');
        if (!h) {
          h = Object.assign(document.createElement('div'), { className: 'h' });
          Object.assign(h.style, {
            position: 'absolute',
            left: '0',
            right: '0',
            /* EDIT → horizontal line thickness */
            height: '1px',
            /* EDIT → horizontal line color     */
            background: '#d61f27',
            transformOrigin: 'left center'
          });
          overlay.appendChild(h);
        }
        h.style.top = (100 - y_value) + '%';  // invert Y so 0 % is bottom
    
        /* animate once per update */
        if (typeof gsap !== 'undefined') {
          /* EDIT → animation duration & easing */
          const anim = { duration: 0.4, ease: 'power2.out' };
          gsap.fromTo(v, { scaleY: 0 }, { scaleY: 1, ...anim });
          gsap.fromTo(h, { scaleX: 0 }, { scaleX: 1, ...anim });
        } else {
          /* CSS‑only fallback */
          [v, h].forEach(el => {
            /* EDIT → fallback animation duration & easing */
            el.style.transition = 'transform 0.4s ease-out';
            el.style.transform  = 'scale(0)';
            requestAnimationFrame(() => { el.style.transform = 'scale(1)'; });
          });
        }
      }
    })();
    


    I tested it and works great, but unfortunately I don't know how to share a .story file here. 🙃

4 Replies

  • I've tidied up my code a bit and this demo shows the logic behind what I'm doing:

    I start with two lines x_line and y_line on the screen each 40px long. I capture my two values x_value and y_value and then use the calculations shown on the screen within my JavaScript to work out where I want my lines to be on the x and y axis respectively.

    Inside my JavaScript routine to position the horizontal (brown) line I use:

    // Directly set line position
      gsap.set(y_line, {
      x: 780,
      y: y,
      scaleX: 0 
    });

    // Animate the line being drawn
      gsap.to(y_line, {
      duration: 1.8,
      scaleX: Scale,
      ease: "power2.out"
    });

    and similar code to position the vertical line using ScaleY.

    The Scale variable I use is set to 1600 and I have absolutely NO IDEA WHY - other than it works. If  anyone can shed some light on this parameter I would be grateful.

    Also - I position each line in the middle of where they need to be and the Scale method expands each line from the centre - I don't seem to be able to anchor the line and expand from the left and from the bottom - any suggestions??

  • Hello John,

    I don’t recommend using the Storyline lines for a few reasons. Instead, you can create a 100% transparent shape (don't use a shape with no fill! add a fill and set the transparency to 100%) and use the code below. 

    Don’t forget to change the data-model-id and execute the JavaScript when the timeline starts.

    How the code works

    Overlay injection – A full‑size overlay (.line-overlay) is appended inside the 100% transparent shape container (data-model-id="6Wm53ht8QUh").

    Dynamic lines – Two absolutely positioned <div> elements (vertical and horizontal) are placed inside the overlay. Their left and top are set with percentages, so they stay in the correct spot even when the slide scales.

    Polling loop – Every pollRateMs milliseconds the script compares Storyline variables x_value and y_value to the previous values. When either changes, the lines are redrawn and animated once (GSAP if available, CSS transition otherwise).

    How to customise

    Update frequency – Edit the pollRateMs constant (e.g., 100 for 10 updates per second).

    Line thickness – Change width (vertical line) or height (horizontal line) in the marked “thickness” style lines.

    Line colour – Adjust the background property in the marked “color” style lines.

    Animation speed / easing – Modify duration and ease in the GSAP block, or the transition shorthand in the CSS‑fallback block.

    /*  Live tracking + single animation   */
    (function () {
    
      /* EDIT → change how often the script checks for new values (ms) */
      const pollRateMs = 200;             // 5× per second
    
      /* internal refs */
      const graphSelector = '[data-model-id="6cPIlzq1zZH"]';
      const player        = GetPlayer();
      let   lastX         = player.GetVar('x_value');
      let   lastY         = player.GetVar('y_value');
    
      /* first draw */
      drawLines(lastX, lastY);
    
      /* watch Storyline variables */
      setInterval(() => {
        const x = player.GetVar('x_value');
        const y = player.GetVar('y_value');
        if (x !== lastX || y !== lastY) {
          drawLines(x, y);
          lastX = x;
          lastY = y;
        }
      }, pollRateMs);
    
      /* ---------- core ---------- */
      function drawLines(x_value, y_value) {
        const graph = document.querySelector(graphSelector);
        if (!graph) return;
    
        /* overlay that stretches 100 % of the graph */
        let overlay = graph.querySelector('.line-overlay');
        if (!overlay) {
          overlay = Object.assign(document.createElement('div'), {
            className: 'line-overlay'
          });
          Object.assign(overlay.style, {
            position: 'absolute',
            inset: '0',
            pointerEvents: 'none'
          });
          graph.appendChild(overlay);
        }
    
        /* vertical line */
        let v = overlay.querySelector('.v');
        if (!v) {
          v = Object.assign(document.createElement('div'), { className: 'v' });
          Object.assign(v.style, {
            position: 'absolute',
            top: '0',
            bottom: '0',
            /* EDIT → vertical line thickness  */
            width: '1px',
            /* EDIT → vertical line color      */
            background: '#d61f27',
            transformOrigin: 'bottom center'
          });
          overlay.appendChild(v);
        }
        v.style.left = x_value + '%';
    
        /* horizontal line */
        let h = overlay.querySelector('.h');
        if (!h) {
          h = Object.assign(document.createElement('div'), { className: 'h' });
          Object.assign(h.style, {
            position: 'absolute',
            left: '0',
            right: '0',
            /* EDIT → horizontal line thickness */
            height: '1px',
            /* EDIT → horizontal line color     */
            background: '#d61f27',
            transformOrigin: 'left center'
          });
          overlay.appendChild(h);
        }
        h.style.top = (100 - y_value) + '%';  // invert Y so 0 % is bottom
    
        /* animate once per update */
        if (typeof gsap !== 'undefined') {
          /* EDIT → animation duration & easing */
          const anim = { duration: 0.4, ease: 'power2.out' };
          gsap.fromTo(v, { scaleY: 0 }, { scaleY: 1, ...anim });
          gsap.fromTo(h, { scaleX: 0 }, { scaleX: 1, ...anim });
        } else {
          /* CSS‑only fallback */
          [v, h].forEach(el => {
            /* EDIT → fallback animation duration & easing */
            el.style.transition = 'transform 0.4s ease-out';
            el.style.transform  = 'scale(0)';
            requestAnimationFrame(() => { el.style.transform = 'scale(1)'; });
          });
        }
      }
    })();
    


    I tested it and works great, but unfortunately I don't know how to share a .story file here. 🙃

    • JohnCooper-be3c's avatar
      JohnCooper-be3c
      Community Member

      Awesome! Looks great - can't wait to try it out. I love the idea of live tracking the variables and the transparent overlay over the graph saves a lot of messing about working out max and min x and y co-ordinates. Thanks. I will let you know how I get on....

    • JohnCooper-be3c's avatar
      JohnCooper-be3c
      Community Member

      You are a real hero! I dispensed with the tracking as I only needed to draw the graph once - but your solution worked absolutely perfectly even when I loaded it under the LearnDash LMS (which my solution didn't!!). Full kudos to you... Brilliant, brilliant coding!!