var fabricCanvas = this.__canvas = new fabric.Canvas('c');
fabricCanvas.setHeight(300);
fabricCanvas.setWidth(600);
const LineWithArrow = fabric.util.createClass(fabric.Line, {
type: 'line_with_arrow',
initialize(element, options) {
options || (options = {});
this.callSuper('initialize', element, options);
this.set({
hasBorders: false,
hasControls: false,
});
},
_render(ctx) {
this.callSuper('_render', ctx);
ctx.save();
const xDiff = this.x2 - this.x1;
const yDiff = this.y2 - this.y1;
const angle = Math.atan2(yDiff, xDiff);
ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(-5, 5);
ctx.lineTo(-5, -5);
ctx.closePath();
ctx.fillStyle = this.stroke;
ctx.fill();
ctx.restore();
},
toObject() {
return fabric.util.object.extend(this.callSuper('toObject'), {
customProps: this.customProps,
});
},
});
const WavyLineWithArrow = fabric.util.createClass(fabric.Group, {
type: 'wavy_line_with_arrow',
initialize(points, options) {
options || (options = {});
this.coord_x1 = points[0];
this.coord_y1 = points[1];
this.coord_x2 = points[2];
this.coord_y2 = points[3];
this.arrowSize = options.arrowSize || 10;
const selfOptions = fabric.util.object.clone(options);
selfOptions.top = this.coord_y1;
selfOptions.left = this.coord_x1;
this.set({
width: this.coord_x2 - this.coord_x1,
height: this.coord_y2 - this.coord_y1,
top: this.coord_y1,
left: this.coord_x1
});
this.setCoords();
this._funct_ = selfOptions.funct;
if(this._funct_ === null || this._funct_ === undefined) {
this._funct_ = function(x) {
return Math.sin(x) * 10;
};
}
this.period = selfOptions.period;
if(!this.period) {
this.period = 1;
}
this.updateCoords = () => {
this.set({
width: this.coord_x2 - this.coord_x1,
height: this.coord_y2 - this.coord_y1,
top: this.coord_y1,
left: this.coord_x1
});
this.setCoords();
};
Object.defineProperty(this, 'x1', {
set: (x1) => {
this.coord_x1 = x1;
this.updateCoords();
this.updateInternalPointsData();
this.dirty = true;
},
get: () => {
return this.coord_x1;
}
});
Object.defineProperty(this, 'x2', {
set: (x2) => {
this.coord_x2 = x2;
this.updateCoords();
this.updateInternalPointsData();
this.dirty = true;
},
get: () => {
return this.coord_x2;
}
});
Object.defineProperty(this, 'y1', {
set: (y1) => {
this.coord_y1 = y1;
this.updateCoords();
this.updateInternalPointsData();
this.dirty = true;
},
get: () => {
return this.coord_y1;
}
});
Object.defineProperty(this, 'y2', {
set: (y2) => {
this.coord_y2 = y2;
this.updateCoords();
this.updateInternalPointsData();
this.dirty = true;
},
get: () => {
return this.coord_y2;
}
});
Object.defineProperty(this, 'funct', {
set: (value) => {
this._funct_ = value;
if(value) {
this.period = 1;
if(value[0]) {
this._funct_ = value[0];
}
if(value[1]) {
this.period = value[1] || 1;
}
}
this.updateInternalPointsData();
this.dirty = true;
},
get: () => {
return this._funct_;
}
});
this.updateInternalPointsData = () => {
const headSize = 20;
const basicScaleFactorX = 0.2;
const scaleFactorY = 1.0;
const arrowSize = this.arrowSize || 10;
this.coord_x1 = this.left;
this.coord_y1 = this.top;
this.coord_x2 = this.coord_x1 + this.width;
this.coord_y2 = this.coord_y1 + this.height;
const len = this.width;
const polyPoints = [];
let periodRescaleFactor = this.period/basicScaleFactorX * Math.floor((len-headSize) / (this.period/basicScaleFactorX)) / (len-headSize);
if(periodRescaleFactor === undefined || periodRescaleFactor < 0.001) {
periodRescaleFactor = 1;
}
const scaleFactorX = basicScaleFactorX * periodRescaleFactor;
if(this._funct_ === null || this._funct_ === undefined) {
this._funct_ = function(x) {
return Math.sin(x) * 10;
};
this.period = Math.PI * 2;
}
if(!this.period) {
this.period = 1;
}
var step = 0.5;
for(var x=0; x<len-headSize-step; x+=step) {
polyPoints.push({
x: x-len/2,
y: this._funct_(x*scaleFactorX)*scaleFactorY
});
}
polyPoints.push({x: len/2-headSize-step, y: 0});
polyPoints.push({x: len/2, y: 0});
this.forEachObject(function(o) {
this.remove(o);
}, this);
for(var i=1;i<polyPoints.length;++i) {
this.add(new fabric.Line([
polyPoints[i-1].x,
polyPoints[i-1].y,
polyPoints[i].x,
polyPoints[i].y
], options));
}
const arrOptions = fabric.util.object.clone(options);
arrOptions.left = len/2;
arrOptions.top = -arrowSize/2;
this.add(new fabric.Polyline([
{x: len/2, y: -arrowSize/2},
{x: len/2 + arrowSize/2, y: 0},
{x: len/2, y: arrowSize/2},
{x: len/2, y: -arrowSize/2}
], arrOptions));
};
this.callSuper('initialize', [], selfOptions);
this.updateInternalPointsData();
this.set({
hasBorders: true,
hasControls: true,
});
},
render(ctx) {
this.updateInternalPointsData();
this.callSuper('render', ctx);
},
toObject() {
return fabric.util.object.extend(this.callSuper('toObject'), {
customProps: this.customProps,
x1: this.x1,
x2: this.x2,
y1: this.y1,
y2: this.y2,
arrowSize: this.arrowSize,
period: this.period,
funct: this._funct_
});
},
});
drawLineWithArrow = (item, points, color) => (
new LineWithArrow(points, {
customProps: item,
strokeWidth: 2,
stroke: color,
})
)
drawWavyLineWithArrow = (item, points, color, funct) => (
new WavyLineWithArrow(points, {
customProps: item,
strokeWidth: 2,
stroke: color,
funct: funct
})
)
selectLine = (item, points) => {
switch (item.type) {
case 'line_with_arrow':
return this.drawLineWithArrow(item, points, fabric.Color.fromRgb("rgb(255,0,0)"));
case 'wavy_line_with_arrow':
return this.drawWavyLineWithArrow(item, points, fabric.Color.fromRgb("rgb(255,0,0)"));
}
return null;
}
let line;
let isDown;
let typesOfLinesIter = -1;
const typesOfLines = [
null,
[
function(x) { return Math.max(-10, Math.min(Math.tan(x/2) / 3, 10)); },
4 * Math.PI
]
];
fabricCanvas.on('mouse:down', (options) => {
isDown = true;
once = true;
const pointer = fabricCanvas.getPointer(options.e);
const points = [pointer.x, pointer.y, pointer.x, pointer.y];
const item = {
type: 'wavy_line_with_arrow'
};
line = this.selectLine(item, points);
++typesOfLinesIter;
typesOfLinesIter %= typesOfLines.length;
line.set({ funct: typesOfLines[typesOfLinesIter] });
fabricCanvas
.add(line)
.setActiveObject(line)
.renderAll();
});
fabricCanvas.on('mouse:move', (options) => {
if (!isDown) return;
const pointer = fabricCanvas.getPointer(options.e);
line.set({ x2: pointer.x, y2: pointer.y });
fabricCanvas.renderAll();
});
fabricCanvas.on('mouse:up', () => {
isDown = false;
line.setCoords();
fabricCanvas.setActiveObject(line).renderAll();
});
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.14.2/TweenMax.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.8/fabric.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c"></canvas>