<svg class="mainSVG" viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
        <defs>
              <filter id="glow" x="-100%" y="-100%" width="350%" height="350%" color-interpolation-filters="sRGB">
                <feGaussianBlur stdDeviation="5" result="coloredBlur" />
                <feOffset dx="0" dy="20" result="offsetblur"></feOffset>
                <feFlood id="glowAlpha" flood-color="#000" flood-opacity="0.123"></feFlood>
                <feComposite in2="offsetblur" operator="in"></feComposite>
                <feMerge>
                  <feMergeNode/>
                  <feMergeNode in="SourceGraphic"></feMergeNode>
                </feMerge>
              </filter>
        </defs>

        <g id="horizontalLinesGroup"   fill="none" stroke="#FFF" stroke-miterlimit="10">
            <line id="bottomLine" x1="149.72" y1="467.16" x2="611.66" y2="467.16" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="3"/>
            <line id="vert" x1="149.72" y1="467.16" x2="149.72" y2="120.11" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="3"/>
            <polygon id="left_arr" stroke-width="0" points="609.86 461.68 609.86 471.93 620.29 467.16 609.86 461.68" fill="#aaa"/>
            <polygon id="up_arr" stroke-width="0" points="144.6 121.58 154.85 121.58 150.08 111.15 144.6 121.58" fill="#aaa"/>
        </g>

        <g id="uiGroup">
            <g id="v_dash">
              <g>
                <line x1="401.5" y1="466.8" x2="401.5" y2="460.8" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="1"/>
                <line x1="401.5" y1="449.28" x2="401.5" y2="339.78" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="1" stroke-dasharray="11.53 11.53"/>
                <line x1="401.5" y1="334.02" x2="401.5" y2="328.02" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="1"/>
              </g>
            </g>
            <g id="h_dash">
              <g>
                <line x1="401.5" y1="327" x2="395.5" y2="327" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="1"/>
                <line x1="384.08" y1="327" x2="161.43" y2="327" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="1" stroke-dasharray="11.42 11.42"/>
                <line x1="155.72" y1="327" x2="149.72" y2="327" fill="none" stroke="#aaa" stroke-miterlimit="10" stroke-width="1"/>
              </g>
            </g>

            <path id="graphLine" d="M187.06,168.76S278,321.66,400,326.52s210.16-135.1,210.16-135.1" fill="none" stroke-linecap="round" stroke="#2665BA" stroke-width="4" stroke-miterlimit="10"/>
            <path id="train_err_line" d="M185.1,222.7c22.6,47.7,64.7,115.6,124.5,158.1c69,49,136.8,53.6,304.2,53.4" fill="none" stroke-linecap="round" stroke="#F79819" stroke-width="4" stroke-miterlimit="10"/>

            <g id="connectorGroup">
                <line id="connector" x1="1000" x2="1000" y1="0" y2="0" stroke="#5ea0fa" />
                <line id="connector2" x1="1000" x2="1000" y1="0" y2="0" stroke="#5ea0fa" />
                <line id="connector3" x1="1000" x2="1000" y1="0" y2="0" stroke="#5ea0fa" />
            </g>

            <text class="axes" transform="translate(75 120)">Error</tspan></text>
            <text class="legend" transform="translate(620.09 190)">dev error</tspan></text>
            <text class="legend" transform="translate(620.09 438)">train error</text>
            <text class="axes" transform="translate(560 530)">Number of Epochs</text>

            <g id="box">
                <rect x="0" width="160" height="40" rx="20" ry="20" fill="#f0f0f0"/>
                <text id="boxLabel" x="80" y="25"></text>
            </g>

            <g id="box2">
                <rect x="0" width="170" height="40" rx="20" ry="20" fill="#f0f0f0"/>
                <text id="boxLabel2" x="85" y="25"></text>
            </g>
        </g>

        <circle id="nullDot" fill="red" cx="0" cy="0" r="0"/>
        <circle id="graphDot" fill="#5ea0fa" cx="0" cy="0" r="10"/>
        <circle id="interDot" fill="#F79819" cx="0" cy="0" r="8"/>
        <circle id="dragger" fill="rgba(0, 137, 236, 0.2)" cx="0" cy="0" r="15" stroke="rgba(95, 190, 237, 0.05)" stroke-width="10"/>

        <linearGradient id="boxGrad" gradientUnits="userSpaceOnUse" x1="65.7809" y1="25.7808" x2="14.2194" y2="25.7808" gradientTransform="matrix(-5.857245e-007 -1 1 -5.872379e-007 14.2192 65.7809)">
          <stop  offset="0.3858" style="stop-color:#E4386D"/>
          <stop  offset="0.7513" style="stop-color:#CF2156"/>
        </linearGradient>

    </svg>
body {
  background-color:#ffffff;
  overflow: hidden;
  /* font-family: 'Alegreya Sans', sans-serif; */

}

body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}


svg{
  position:absolute;
  width:100%;
  height:100%;
  visibility:hidden;

}


.mainSVG{
  position:absolute;
  width:100%;
  height:100%;
  visibility:hidden;
  /*  top:200px; */
  left:50%;
  transform:translate(-50%, 0%);
  overflow:visible;
}

#boxLabel, #boxLabel2{
  text-anchor:middle;
  fill:#115F9A;
  font-size:16px;
  user-select:none;
  -webkit-user-select:none;
  pointer-events:none;
  font-family: 'Roboto', sans-serif;
  font-weight:700;
}

.legend {
    fill: #aaa;
    font-size:14px;
    font-family: 'Roboto', sans-serif;
}

.axes {
    fill: #878181;
    font-size:15px;
    font-family: 'Roboto', sans-serif;
}

#box, #box2{
  opacity:0;
}
circle{
  -webkit-tap-highlight-color: rgba(0,0,0,0);
}
var xmlns = "http://www.w3.org/2000/svg",
  xlinkns = "http://www.w3.org/1999/xlink",
  select = function(s) {
    return document.querySelector(s);
  },
  selectAll = function(s) {
    return document.querySelectorAll(ds);
  },
  mainSVG = select('.mainSVG'),
  box = select('#box'),
  box2 = select('#box2'),
  connector = select('#connector'),
  connector2 = select('#connector2'),
  connector3 = select('#connector3'),
  train_line = select('#train_err_line'),
  // connectorGroup = select('#connectorGroup'),
  dragger = select('#dragger'),
  graphDot = select('#graphDot'),
  interDot = select('#interDot'),
  boxLabel = select('#boxLabel'),
  nullDot = select('#nullDot'),
  graphLine = select('#graphLine'),
  graphBezier = MorphSVGPlugin.pathDataToBezier(graphLine.getAttribute('d')),
  perc, boxPos = {x: 0, y: 0},
  boxPos2 = {x: 0, y: 0},
  isPressed = false,
  bottomLine = select('#bottomLine'),
  labelOffset = 8;

TweenMax.set('svg', {visibility: 'visible'})
TweenMax.set([dragger, graphDot, nullDot, interDot], {transformOrigin: '50% 50%'})
TweenMax.set([dragger, graphDot, nullDot], {x: graphBezier[0].x, y: graphBezier[0].y})
TweenMax.set([box, box2], {transformOrigin: '50% 100%'})
TweenMax.set(interDot, {alpha: 0, scale: 0})
//fix boxes at certain axes
TweenMax.set(box, {x: (bottomLine.getAttribute('x1') - labelOffset - (box.getBBox().width))})
TweenMax.set(box2, {y: 467.1610 + labelOffset})

let tl = new TimelineMax({paused: true});
init()

function init() {
    tl.to([graphDot, dragger], 5, {
      bezier: {
        type: "cubic",
        values: graphBezier,
        autoRotate: false
      },
      ease: Linear.easeNone
    })

    Draggable.create(nullDot, {
        // type: 'x', //horizontal movement only
        trigger: dragger,
        onPress: graphPress,
        bounds: {
          minX: graphBezier[0].x,
          maxX: graphBezier[graphBezier.length-1].x
      },
        zIndexBoost:false,
        onDrag: updateGraph,
        onRelease: graphRelease,
    })
}

function graphPress() {
    isPressed = true;
    TweenMax.to(dragger, 0.5, {
        attr: {r: 30},
        ease: Elastic.easeOut.config(1, 0.7)
    })

    appendGraphEle();
    TweenMax.to([box, box2, interDot], 0.5, {
        scale: 1,
        alpha: 1,
        ease: Elastic.easeOut.config(1.2, 0.7)
    });
}

function graphRelease() {
    appendGraphEle()
    isPressed = false;
    TweenMax.to(dragger, 0.2, {
      attr: {r: 15},
      ease: Elastic.easeOut.config(0.7, 0.7)
    })

    appendGraphEle()
    TweenMax.to([box, box2, interDot], 0.2, {
      scale: 0,
      alpha: 0,
    })
}

function updateGraph() {
    perc = (nullDot._gsTransform.x - graphBezier[0].x) / (graphBezier[graphBezier.length-1].x - graphBezier[0].x);
    TweenMax.to(tl, 0.0005, {
      progress: perc
    })
    appendGraphEle()
}

function appendGraphEle() {
  drawLines();
  drawBoxes();
}

function drawLines() {

      if (isPressed) {
        TweenMax.set(connector, { //vertical line
          attr: {
            x1: graphDot._gsTransform.x,
            y1: 467.1610 + labelOffset,
            x2: graphDot._gsTransform.x,
            y2: graphDot._gsTransform.y
          }
        })

        TweenMax.set(connector2, { //horizontal line ok
          attr: {
            x1: bottomLine.getAttribute('x1') - labelOffset,
            y1: graphDot._gsTransform.y,
            x2: graphDot._gsTransform.x,
            y2: graphDot._gsTransform.y
          }
        })

        findAndDrawIntersectionDot();

      } else {

        TweenMax.to([connector, connector2, connector3], 0.1, {
          attr: {
            x1: graphDot._gsTransform.x,
            y1: graphDot._gsTransform.y,
            x2: graphDot._gsTransform.x,
            y2: graphDot._gsTransform.y
          }
        })
      }
}

function drawBoxes() {
    // console.log("updating box pos " + boxPos.x)
    // boxPos.x = connector2.getAttribute("x1") - (box.getBBox().width);
    boxPos.y = connector2.getAttribute("y1") - (box.getBBox().height * 0.5);
    boxPos2.x = connector.getAttribute("x1") - (box2.getBBox().width /2);

    TweenMax.to(box, 0.01, {
      y: boxPos.y,
    })

    TweenMax.to(box2, 0.01, {
      x: boxPos2.x,
    })

    let curr_iter = mapTo6000(dragger._gsTransform.x, 59000);
    if ((curr_iter < 31000) && (curr_iter > 29000)) {
        curr_iter = 30000;
    }

    let err_msg = " ";
    if (curr_iter == 30000) {
        err_msg = "Optimal dev error"
    } else if (curr_iter < 30000) {
        err_msg = "Model underfitting"
    } else {
        err_msg = "Model overfitting"
    }
    boxLabel.textContent = err_msg //.toFixed(2);
    boxLabel2.textContent = parseInt(curr_iter) + "th epoch" //.toFixed(2);
}

function mapTo6000(currX, range) {
    let currRange = Math.floor(graphBezier[graphBezier.length - 1].x - graphBezier[0].x)
    return Math.floor((currX - graphBezier[0].x) / currRange * range)
}

function findAndDrawIntersectionDot() {
    let path_d = convertFromLineToPath(connector);
    let pt = Snap.path.intersection(path_d, train_line.getAttribute('d'))
    if(pt[0]) {
        TweenMax.set(interDot, {
            x: pt[0].x,
            y: pt[0].y,
        })
    }
}

function convertFromLineToPath(line) {
    let str = "M"+line.getAttribute('x2')+","+line.getAttribute('y2')+"V"+line.getAttribute('y1');
    return str;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/utils/Draggable.min.js
  3. //s3-us-west-2.amazonaws.com/s.cdpn.io/16327/DrawSVGPlugin.js?r=12
  4. //s3-us-west-2.amazonaws.com/s.cdpn.io/16327/MorphSVGPlugin.min.js
  5. https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js