title: Fabric custom object
body: <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>fabric.ThreePointArc = fabric.util.createClass(fabric.Circle, {
          type: 'threePointArc',
  
          points: [], //array of startPoint, intermediatePoint, endPoint
  
          arcCenter: new fabric.Point(null, null),
  
          arcBounds: null,
  
          radius: null,
  
          initialize: function (points, options) {
  
              if (!points || points.length === 0) {
                  return;
              }
  
              this.points = points;
  
              this.callSuper('initialize', options);
  
              // supports only originX and originY as center
              this.originX = this.originY = 'center';
          },
  
          _set: function(key, value) {
  
              this.callSuper('_set', key, value);
  
              if (key === 'points') {
                  this._calcArcCenter();
                  this._calcDimensions();
                  this.setCoords();
              }
              return this;
          },
  
          setRadius: function(value) {
              this.radius = value;
              return this;
          },
  
          _calcDimensions: function() {
  
              this._calcArcAngles();
              this.setRadius(cMath.getLength(this.arcCenter, this.points[0]));
              this._calcArcBounds();
  
              this.width = this.arcBounds.width;
              this.height = this.arcBounds.height;
              
              this.top = this.arcBounds.y + this.arcBounds.height / 2;
              this.left = this.arcBounds.x + this.arcBounds.width / 2;
          },
  
          _calcArcCenter: function() {
              var c1Mp = cMath.getMidPoint(this.points[0], this.points[1]), // chord 1 midpoint
                  c2Mp = cMath.getMidPoint(this.points[1], this.points[2]), // chord 2 midpoint
                  c1pm = -(1 / cMath.getSlope(this.points[0], this.points[1])), // chord 1 perpendicular bisector slope
                  c2pm = -(1 / cMath.getSlope(this.points[1], this.points[2])); // chord 2 perpendicular bisector slope
  
              // chord perpendicular bisectors meet at the center
              this.arcCenter.x = (c2Mp.y - (c2pm * c2Mp.x) + (c1pm * c1Mp.x) - c1Mp.y) / (c1pm - c2pm);
              this.arcCenter.y = (c2pm * (this.arcCenter.x - c2Mp.x)) + c2Mp.y;
          },
  
          _calcArcBounds: function() {
              var validPoints = this.buildValidPointsForArc(),
                  minX = fabric.util.array.min(validPoints, 'x'),
                  minY = fabric.util.array.min(validPoints, 'y'),
                  maxX = fabric.util.array.max(validPoints, 'x'),
                  maxY = fabric.util.array.max(validPoints, 'y'),
                  width = (maxX - minX) || 1,
                  height = (maxY - minY) || 1;
  
              this.arcBounds = {
                  x: minX,
                  y: minY,
                  width: width,
                  height: height
              }
          },
  
          buildValidPointsForArc: function() {
              var direction = this.getRenderingDirection(),
                  possibleBoundingPoints = this.points.concat();
  
              !this.arcAngles &amp;&amp; this._calcArcAngles();
  
              if (direction) {
                  for (var i = 1; i &lt;= 4; i++) {
                      var randomAngle = i * (PI / 2);
  
                      if (this.arcAngles.startAngle &lt; this.arcAngles.endAngle) {
                          !(this.arcAngles.startAngle &lt;= randomAngle &amp;&amp; randomAngle &lt;= this.arcAngles.endAngle) &amp;&amp;
                          possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
                      } else {
                          (this.arcAngles.endAngle &lt;= randomAngle &amp;&amp; randomAngle &lt;= this.arcAngles.startAngle) &amp;&amp;
                          possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
                      }
                  }
  
              } else {
                  for (var i = 4; i &gt;= 1; i--) {
                      var randomAngle = i * (PI / 2);
  
                      if (this.arcAngles.startAngle &lt; this.arcAngles.endAngle) {
                          (this.arcAngles.startAngle &lt;= randomAngle &amp;&amp; randomAngle &lt;= this.arcAngles.endAngle) &amp;&amp;
                          possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
                      } else {
                          !(this.arcAngles.endAngle &lt;= randomAngle &amp;&amp; randomAngle &lt;= this.arcAngles.startAngle) &amp;&amp;
                          possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
                      }
                  }
              }
  
              return possibleBoundingPoints;
          },
  
          generateArcPoint: function(angle) {
              return new fabric.Point(this.arcCenter.x + this.radius * Math.cos(angle), this.arcCenter.y + this.radius * Math.sin(angle));
          },
  
          _calcArcAngles: function() {
  
              var angleKeyRepo = ["startAngle", "intermediateAngle", "endAngle"];
              this.arcAngles = this.arcAngles || {};
  
              this.points.forEach(function(point, index) {
                  var a = cMath.getAngle(this.arcCenter, point);
  
                  this.arcAngles[angleKeyRepo[index]] = a &lt; 0 ? ((PI * 2) + a) :
                      a &gt; 2 * PI ? ((PI * 2) - a) : a;
              }, this);
          },
  
          getRenderingDirection: function() {
              return (((this.points[1].x - this.points[0].x) * (this.points[2].y - this.points[0].y)) -
                  ((this.points[1].y - this.points[0].y) * (this.points[2].x - this.points[0].x))) &lt; 0;
          },
  
          _render: function(ctx, noTransform) {
              if (!this.visible) {
                  return;
              }
  
              ctx.beginPath();
              ctx.arc(
                  noTransform ? this.left : 0,
                  noTransform ? this.top : 0,
                  this.radius,
                  this.arcAngles.startAngle,
                  this.arcAngles.endAngle,
                  this.getRenderingDirection()
              );
              this._renderFill(ctx);
              this._renderStroke(ctx);
          },
  
          toObject: function (propertiesToInclude) {
             return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
                 points: this.points
             });
         }
      });
  
  fabric.ThreePointArc.fromObject = function(object) {
      return new fabric.ThreePointArc(object.points, object);
  };
  
  fabric.ThreePointArc.async = false;
  
  fabric.util.cMath = {
    getSlope : function (startPoint, endPoint) {
  
          if (!startPoint || !endPoint) {
              console.error('startPoint and endPoint are required to evaluate slope');
              return;
          }
  
          // hack to get around the indefinte slope problem
          if (endPoint.x == startPoint.x) startPoint.x = startPoint.x + 0.01;
          if (endPoint.y == startPoint.y) endPoint.y = endPoint.y + 0.01;
  
          return (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x);
      },
  
      getMidPoint: function (startPoint, endPoint) {
  
          if (!startPoint || !endPoint) {
              console.error('startPoint and endPoint are required to evaluate slope');
              return;
          }
  
          return { x: (startPoint.x + endPoint.x) / 2, y: (startPoint.y + endPoint.y) / 2 };
      },
  
      getAngle: function (startPoint, endPoint, isDegree) {
  
          if (!startPoint || !endPoint) {
              console.error('startPoint and endPoint are required to evaluate slope');
              return;
          }
  
          var radians = Math.atan2((endPoint.y - startPoint.y), (endPoint.x - startPoint.x)),
              degrees = fabric.util.radiansToDegrees(radians);
  
          return isDegree ? degrees &lt; 0 ? 360 + degrees : degrees : radians;
      },
  
      getLength: function (startPoint, endPoint) {
  
          if (!startPoint || !endPoint) {
              console.error('startPoint and endPoint are required to evaluate slope');
              return;
          }
  
          return Math.sqrt(Math.pow(endPoint.y - startPoint.y, 2) + Math.pow(endPoint.x - startPoint.x, 2));
      }
  }
  
  var canvas  = new fabric.Canvas('c');
  
  var startPoint = new fabric.Point(47.25423728813553, 56.91525423728814),
      intermediatePoint = new fabric.Point( 76.33898305084739,19.8983050847458 ),
      endPoint = new fabric.Point( 105.42372881355931,86 );
  
   var arc = new fabric.ThreePointArc([startPoint, intermediatePoint, endPoint] , {
          fill:  "#FF0000",
          stroke: "#000",
          strokeWidth: 10
      });
      
  canvas.add(arc);</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.10/fabric.js"&gt;&lt;/script&gt;
  &lt;canvas id="c" width="800" height="800"&gt;&lt;/canvas&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p>I'm trying to create a three point arc on fabric js (ver: 1.4.10).  What I want is exactly like this:- <a href="https://www.screencast.com/t/dMLvcOduEF" rel="nofollow noreferrer">https://www.screencast.com/t/dMLvcOduEF</a> </p>
  
  <p>I have a points array from which I'm calcualting the startAngle, endAngle, arcCenter, radius, bounding boxc height and widht, top and left. When I draw it, the actual object is not drawing where my mouse points. It adds up the left and top value with the arcCenter.x and arcCenter.y respectively and draws there. Is there any workaround so that my arc can be drawn where my pointer is ?. So that I don't need to write any extra lines of code for moving, rotating and scaling.  </p>
  
  <p>What I'm getting is like this:- <a href="https://www.screencast.com/t/V9MUgB3pB" rel="nofollow noreferrer">https://www.screencast.com/t/V9MUgB3pB</a> </p>
  
  <p>added the fiddle!!</p>
  
  <p>Is there any workaround for this ??</p>