text: /**
  * Copyright 2012-2022, Plotly, Inc.
  * All rights reserved.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
  
  'use strict';
  
  var mod = require('./mod').mod;
  
  /*
   * look for intersection of two line segments
   *   (1->2 and 3->4) - returns array [x,y] if they do, null if not
   */
  exports.segmentsIntersect = segmentsIntersect;
  function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
      var a = x2 - x1;
      var b = x3 - x1;
      var c = x4 - x3;
      var d = y2 - y1;
      var e = y3 - y1;
      var f = y4 - y3;
      var det = a * f - c * d;
      // parallel lines? intersection is undefined
      // ignore the case where they are colinear
      if(det === 0) return null;
      var t = (b * f - c * e) / det;
      var u = (b * d - a * e) / det;
      // segments do not intersect?
      if(u < 0 || u > 1 || t < 0 || t > 1) return null;
  
      return {x: x1 + a * t, y: y1 + d * t};
  }
  
  /*
   * find the minimum distance between two line segments (1->2 and 3->4)
   */
  exports.segmentDistance = function segmentDistance(x1, y1, x2, y2, x3, y3, x4, y4) {
      if(segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return 0;
  
      // the two segments and their lengths squared
      var x12 = x2 - x1;
      var y12 = y2 - y1;
      var x34 = x4 - x3;
      var y34 = y4 - y3;
      var ll12 = x12 * x12 + y12 * y12;
      var ll34 = x34 * x34 + y34 * y34;
  
      // calculate distance squared, then take the sqrt at the very end
      var dist2 = Math.min(
          perpDistance2(x12, y12, ll12, x3 - x1, y3 - y1),
          perpDistance2(x12, y12, ll12, x4 - x1, y4 - y1),
          perpDistance2(x34, y34, ll34, x1 - x3, y1 - y3),
          perpDistance2(x34, y34, ll34, x2 - x3, y2 - y3)
      );
  
      return Math.sqrt(dist2);
  };
  
  /*
   * distance squared from segment ab to point c
   * [xab, yab] is the vector b-a
   * [xac, yac] is the vector c-a
   * llab is the length squared of (b-a), just to simplify calculation
   */
  function perpDistance2(xab, yab, llab, xac, yac) {
      var fcAB = (xac * xab + yac * yab);
      if(fcAB < 0) {
          // point c is closer to point a
          return xac * xac + yac * yac;
      } else if(fcAB > llab) {
          // point c is closer to point b
          var xbc = xac - xab;
          var ybc = yac - yab;
          return xbc * xbc + ybc * ybc;
      } else {
          // perpendicular distance is the shortest
          var crossProduct = xac * yab - yac * xab;
          return crossProduct * crossProduct / llab;
      }
  }
  
  // a very short-term cache for getTextLocation, just because
  // we're often looping over the same locations multiple times
  // invalidated as soon as we look at a different path
  var locationCache, workingPath, workingTextWidth;
  
  // turn a path and position along it into x, y, and angle for the given text
  exports.getTextLocation = function getTextLocation(path, totalPathLen, positionOnPath, textWidth) {
      if(path !== workingPath || textWidth !== workingTextWidth) {
          locationCache = {};
          workingPath = path;
          workingTextWidth = textWidth;
      }
      if(locationCache[positionOnPath]) {
          return locationCache[positionOnPath];
      }
  
      // for the angle, use points on the path separated by the text width
      // even though due to curvature, the text will cover a bit more than that
      var p0 = path.getPointAtLength(mod(positionOnPath - textWidth / 2, totalPathLen));
      var p1 = path.getPointAtLength(mod(positionOnPath + textWidth / 2, totalPathLen));
      // note: atan handles 1/0 nicely
      var theta = Math.atan((p1.y - p0.y) / (p1.x - p0.x));
      // center the text at 2/3 of the center position plus 1/3 the p0/p1 midpoint
      // that's the average position of this segment, assuming it's roughly quadratic
      var pCenter = path.getPointAtLength(mod(positionOnPath, totalPathLen));
      var x = (pCenter.x * 4 + p0.x + p1.x) / 6;
      var y = (pCenter.y * 4 + p0.y + p1.y) / 6;
  
      var out = {x: x, y: y, theta: theta};
      locationCache[positionOnPath] = out;
      return out;
  };
  
  exports.clearLocationCache = function() {
      workingPath = null;
  };
  
  /*
   * Find the segment of `path` that's within the visible area
   * given by `bounds` {left, right, top, bottom}, to within a
   * precision of `buffer` px
   *
   * returns: undefined if nothing is visible, else object:
   * {
   *   min: position where the path first enters bounds, or 0 if it
   *        starts within bounds
   *   max: position where the path last exits bounds, or the path length
   *        if it finishes within bounds
   *   len: max - min, ie the length of visible path
   *   total: the total path length - just included so the caller doesn't
   *        need to call path.getTotalLength() again
   *   isClosed: true iff the start and end points of the path are both visible
   *        and are at the same point
   * }
   *
   * Works by starting from either end and repeatedly finding the distance from
   * that point to the plot area, and if it's outside the plot, moving along the
   * path by that distance (because the plot must be at least that far away on
   * the path). Note that if a path enters, exits, and re-enters the plot, we
   * will not capture this behavior.
   */
  exports.getVisibleSegment = function getVisibleSegment(path, bounds, buffer) {
      var left = bounds.left;
      var right = bounds.right;
      var top = bounds.top;
      var bottom = bounds.bottom;
  
      var pMin = 0;
      var pTotal = path.getTotalLength();
      var pMax = pTotal;
  
      var pt0, ptTotal;
  
      function getDistToPlot(len) {
          var pt = path.getPointAtLength(len);
  
          // hold on to the start and end points for `closed`
          if(len === 0) pt0 = pt;
          else if(len === pTotal) ptTotal = pt;
  
          var dx = (pt.x < left) ? left - pt.x : (pt.x > right ? pt.x - right : 0);
          var dy = (pt.y < top) ? top - pt.y : (pt.y > bottom ? pt.y - bottom : 0);
          return Math.sqrt(dx * dx + dy * dy);
      }
  
      var distToPlot = getDistToPlot(pMin);
      while(distToPlot) {
          pMin += distToPlot + buffer;
          if(pMin > pMax) return;
          distToPlot = getDistToPlot(pMin);
      }
  
      distToPlot = getDistToPlot(pMax);
      while(distToPlot) {
          pMax -= distToPlot + buffer;
          if(pMin > pMax) return;
          distToPlot = getDistToPlot(pMax);
      }
  
      return {
          min: pMin,
          max: pMax,
          len: pMax - pMin,
          total: pTotal,
          isClosed: pMin === 0 && pMax === pTotal &&
              Math.abs(pt0.x - ptTotal.x) < 0.1 &&
              Math.abs(pt0.y - ptTotal.y) < 0.1
      };
  };
  
  /**
   * Find point on SVG path corresponding to a given constraint coordinate
   *
   * @param {SVGPathElement} path
   * @param {Number} val : constraint coordinate value
   * @param {String} coord : 'x' or 'y' the constraint coordinate
   * @param {Object} opts :
   *  - {Number} pathLength : supply total path length before hand
   *  - {Number} tolerance
   *  - {Number} iterationLimit
   * @return {SVGPoint}
   */
  exports.findPointOnPath = function findPointOnPath(path, val, coord, opts) {
      opts = opts || {};
  
      var pathLength = opts.pathLength || path.getTotalLength();
      var tolerance = opts.tolerance || 1e-3;
      var iterationLimit = opts.iterationLimit || 30;
  
      // if path starts at a val greater than the path tail (like on vertical violins),
      // we must flip the sign of the computed diff.
      var mul = path.getPointAtLength(0)[coord] > path.getPointAtLength(pathLength)[coord] ? -1 : 1;
  
      var i = 0;
      var b0 = 0;
      var b1 = pathLength;
      var mid;
      var pt;
      var diff;
  
      while(i < iterationLimit) {
          mid = (b0 + b1) / 2;
          pt = path.getPointAtLength(mid);
          diff = pt[coord] - val;
  
          if(Math.abs(diff) < tolerance) {
              return pt;
          } else {
              if(mul * diff > 0) {
                  b1 = mid;
              } else {
                  b0 = mid;
              }
              i++;
          }
      }
      return pt;
  };
  