I am stuck in applying zoom on the elements (directed graph) generated within the svg. The original code uses the d3js v3 library and is shown below:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>[AF-PATTERN-EDITOR]</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style id="compiled-css" type="text/css">
.node, .link-label {
fill:#ccc;
stroke: #fff;
stroke-width: 2px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
.link-active {
stroke: #555;
stroke-opacity: 5;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 2px;
}
svg {
box-sizing: border-box;
border: 1px solid rgb(212, 212, 212);
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script type="text/javascript">
var width = 960,
height = 500,
resolution = 150,
r = 15;
/*var graph = {
"nodes": [
{"task": "1", "label": "1", "social": "I", "id": 1, "x": 150, "y": 450},
{"task": "2", "label": "2", "social": "G", "id": 2, "x": 300, "y": 150},
{"task": "3", "label": "3", "social": "T", "id": 3, "x": 450, "y": 300}
],
"links": [
{"source": "1", "target": "2", "type": "N:1"},
{"source": "2", "target": "3", "type": "1:N"},
{"source": "1", "target": "3", "type": "1:1"}
]
}*/
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var colors = d3.scale.category20();
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g');
svg.call(d3.behavior.zoom().on("zoom", function () {
svg.attr("transform", "scale(" + d3.event.scale + ")")
}));
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom])
svg.selectAll('.vertical')
.data(d3.range(1, width / resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function(d) { return d * resolution; })
.attr('y1', 0)
.attr('x2', function(d) { return d * resolution; })
.attr('y2', height);
svg.selectAll('.horizontal')
.data(d3.range(1, height / resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function(d) { return d * resolution; })
.attr('x2', width)
.attr('y2', function(d) { return d * resolution; });
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':40,
'refY':0,
'orient':'auto',
'markerWidth':5,
'markerHeight':5,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#777')
.style('stroke','none');
const graph = {nodes: [], links: []};
d3.json("graph.json", function (error, data) {
if (error) throw error;
//console.log(graph.nodes);
//console.log(graph.links);
graph.nodes = data.nodes;
graph.links = data.links;
update();
})
const update = () => {
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('drag', dragged);
var link = svg.selectAll("line.link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr('marker-end','url(#arrowhead)')
.attr("data-source", function (d) {
return d.source;
})
.attr("data-target", function (d) {
return d.target;
})
.attr("x1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.x;
}
})
.attr("x2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.x;
}
})
.attr("y1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.y;
}
})
.attr("y2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.y;
}
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
const links = graph.links.map(l => {
const source = graph.nodes.find(n => n.task === l.source);
const target = graph.nodes.find(n => n.task === l.target);
return {...l, source, target};
});
var linkLabel = svg.selectAll("g.link-label")
.data(links)
.enter()
.append("g")
.attr("class", "link-label")
.attr('transform', d => `translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
linkLabel.append('circle')
.attr('r', 20)
.style("fill", '#fff');
linkLabel.append('text')
.text(d => d.type)
.classed('label', true)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('stroke', 'none')
.style('fill', 'black')
//console.log('L: ', graph.links);
var node = svg.selectAll("svg.node")
.data(graph.nodes)
.enter().append('g');
node.append("circle")
.attr("class", "node")
.attr('node-id', d => d.social)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r * 2)
.call(drag);//
node.append("text")
.attr("class", "label")
.attr('node-id', d => d.social)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('dx', function(d) { return d.x; })
.attr('dy', function(d) { return d.y; })
.text(function(d) { return d.social; });
force.on("tick", function () {
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
.attr("y2", function (d) { return d.target.y; });
linkLabel.attr('transform', d => `translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
/*function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}*/
function dragged(d) {
var x = d3.event.x,
y = d3.event.y,
gridX = round(Math.max(r, Math.min(width - r, x)), resolution),
gridY = round(Math.max(r, Math.min(height - r, y)), resolution);
d3.select(this).attr('cx', d.x = gridX).attr('cy', d.y = gridY);
d3.select(this).attr('dx', d.x = gridX).attr('dy', d.y = gridY);
d3.selectAll(`[data-source='${d.task}']`).attr('x1', d.x).attr('y1', d.y);
d3.selectAll(`[data-target='${d.task}']`).attr('x2', d.x).attr('y2', d.y);
d3.select(`text[node-id='${d.social}']`).attr('dx', d.x).attr('dy', d.y)
linkLabel.attr('transform', d => `translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
}
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
};
</script>
</body>
<html>
I have tried to reuse code regarding zoom functionality.. but to no avail. When I apply it, it generally "freezes" the network and I cannot interact with the network (move the nodes independently), i.e. all the elements move as a whole. How can I apply the zoom without affecting the characteristics of the generated graph?
Related
I'm having an issue with ZXing-js. Its returning a ChecksumException no matter what QR Code I put into it. So its detecting the QRCode but throws an Exception. The following is my vuejs 2.0 code. Please help.
Almost all if this code works. Its just the reading of the QR code part that doesn't
import { BarcodeFormat, DecodeHintType, NotFoundException, ChecksumException, FormatException } from '#zxing/library';
const ZXing = require('#zxing/browser');
Vue.component('qr_scanner_modal',{
prop: [
'videoSource'
],
data: function ()
{
return {
qr_error: null,
qrcanvas: null,
context: null,
qrvideo: null,
hints: null,
formats: null,
videoSource: {},
qr: null,
selected_source: null,
polling: null,
localMediaStream: null,
scanLineDirect: 'down',
scanlineOffset: 0,
qr_title: "",
visible: false,
focused: true,
qr_result: ""
};
},
mixins: [ focusMixin ],
created: function ()
{
EventBus.$on('trigger-qrcode-scanner', (qrTitle) => this.show(qrTitle));
},
mounted: function ()
{
let self = this;
this.$root.$on('bv::modal::show', () => this.$nextTick(() => this.mountQRReader()));
},
methods: {
mountQRReader: function ()
{
const hints = new Map();
const formats = [BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX/*, ...*/];
hints.set(DecodeHintType.POSSIBLE_FORMATS, formats);
hints.set(DecodeHintType.TRY_HARDER, true);
hints.set(DecodeHintType.CHARACTER_SET, 'UTF-8');
hints.set(DecodeHintType.ALSO_INVERTED, true);
this.qr = new ZXing.BrowserQRCodeReader(hints);
this.qrcanvas = this.$refs['qrcanvas'];
this.qrcanvas.width = 400;
this.qrcanvas.height = 400;
// this.qrcanvas = this.$refs;
console.log(this.$refs['qrcanvas']);
this.context = this.$refs['qrcanvas'].getContext('2d');
this.qrvideo = this.$refs['qrvideo'];
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices)
{
this.qr_error = "This browser does not support MediaStreamTrack. Try Chrome.";
console.log("enumerateDevices() not supported.");
}
else
{
this.qr_error = null;
}
let self = this;
navigator.mediaDevices
.enumerateDevices()
.then(function (sourceInfos)
{
let videosource = [];
for (var index = 0; index !== sourceInfos.length; index++)
{
var sourceInfo = sourceInfos[index];
if (sourceInfo.kind === 'videoinput')
{
videosource.push({
id: sourceInfo.deviceId,
name: sourceInfo.label || 'Camera ' + index
});
console.log(sourceInfo);
console.log(videosource);
}
}
self.videoSource = videosource;
})
.catch(function (err)
{
console.log(err.name + ": " + err.message);
});
},
show: function (qrTitle)
{
console.log("Show modal called.");
this.qr_title = qrTitle + " - QR / Magstripe Reader";
this.$bvModal.show('qr_code_scanner');
},
dismiss: function()
{
this.stopScan();
this.$bvModal.hide('qr_code_scanner');
},
selectSource: function (source)
{
this.selected_source = source;
let constraints = {
audio: false,
video: {
facingMode: "environment",
sourceId: source
}
};
navigator.getUserMedia(constraints, this.startScan, this.scanError);
},
read: function (value)
{
console.log('read callback called.');
console.log(value);
if (value !== null && value !== undefined)
{
this.qr_result = value.text;
EventBus.$emit('qr_code_returned', value.text);
this.stopScan();
return;
}
},
startScan: function (stream)
{
this.qrvideo.srcObject = stream;
this.localMediaStream = stream;
this.qrvideo.play();
this.polling = setInterval(this.scan, 400);
},
scanError: function (err)
{
if (err)
{
this.qr_error = err;
}
},
stopScan: function ()
{
clearInterval(this.polling);
if (this.localMediaStream)
{
let track = this.localMediaStream.getVideoTracks();
track[0].stop();
}
},
transposeRect: function (width, height)
{
const rectWidth = width * 0.8;
const rectHeight = height * 0.8;
const xPos = (width - rectWidth) / 2;
const yPos = (height - rectHeight) / 2;
this.context.beginPath();
this.context.strokeStyle = 'red';
this.context.lineWidth = '3';
this.context.rect( xPos,
yPos,
rectWidth,
rectHeight);
this.context.stroke();
this.drawScanLine(yPos,
xPos,
xPos + rectWidth,
yPos + rectHeight);
},
drawScanLine: function (top, left, right, bottom)
{
if (this.scanLineDirect === 'down')
{
this.scanlineOffset = this.scanlineOffset + 4;
}
if (this.scanLineDirect === 'up')
{
this.scanlineOffset = this.scanlineOffset - 4;
}
if (top + this.scanlineOffset > bottom)
{
this.scanLineDirect = 'up';
this.scanlineOffset = this.scanlineOffset - 4;
}
if (top + this.scanlineOffset < top)
{
this.scanLineDirect = 'down';
this.scanlineOffset = this.scanlineOffset + 4;
}
this.context.beginPath();
this.context.strokeStyle = 'red';
this.context.lineWidth = '3';
this.context.moveTo(left, top + this.scanlineOffset);
this.context.lineTo(right, top + this.scanlineOffset);
this.context.closePath();
this.context.stroke();
},
scan: async function ()
{
try
{
if (this.localMediaStream)
{
console.log("Scanning Video Feed.");
const width = this.qrcanvas.getAttribute('width');
const height = this.qrcanvas.getAttribute('height');
this.context.drawImage(this.qrvideo, 0, 0, width, height);
console.log("width: " + width);
console.log("height: " + height);
const code = await this.qr.decodeFromCanvas(this.qrcanvas);
// const code = await this.qr.decode(this.qrcanvas);
this.read(code);
this.transposeRect(width, height);
}
}
catch(err)
{
if (err instanceof NotFoundException) {
console.log('No QR code found.')
}
if (err instanceof ChecksumException) {
console.log('A code was found, but it\'s read value was not valid.')
}
if (err instanceof FormatException) {
console.log('A code was found, but it was in a invalid format.')
}
}
}
},
template: `
<b-modal id="qr_code_scanner"
v-bind:title="qr_title"
v-bind:videoSource='videoSource'
hide-footer >
<div class="alert alert-danger" v-show="qr_error != null">
{{qr_error}}
</div>
<div>
<a href='#' v-for="source in videoSource" v-on:click='selectSource(source.id)' class='btn btn-primary'>#{{source.name}}</a>
</div>
<div class="large-centered col-lg-12 col-md-12 col-sm-12" style="overflow: hidden;">
<input type="text" ref="qr_result" name='qr_result' v-focus="focused" v-model='qr_result' class="form-control" />
<video id="qrvideo" ref="qrvideo" controls="false" style="display: none;"></video>
<canvas id="qrcanvas" ref='qrcanvas' style="overflow: hidden;" width="400" height="400"></canvas>
</div>
<div class="modal-footer">
<a href='#' v-on:click='dismiss()' class='btn btn-primary'>Cancel</a>
</div>
</b-modal>
`
});
I'm expecting it to return a QR Code.
How can I remove limiter and edit __editingSetCrop method so that I can correctly mask a image in fabricjs? In my current implementation, dragging is not smooth.
When you try to drag close to the edges, it breaks and stops moving. Instead, it should keep dragging on the available space. When I rotate the image and double click it moves outside of the box. also it not smooth.
here is my example https://www.awesomescreenshot.com/video/14279604?key=ee3022f0335fd9aa82c66628e7085cb3
const drawTopRightIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0.5, 12)
ctx.moveTo(0, 0)
ctx.lineTo(-12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawTopLeftIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0.5, 12)
ctx.moveTo(0, 0)
ctx.lineTo(12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawBottomLeftIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0, -12)
ctx.moveTo(0, 0)
ctx.lineTo(12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawBottomRightIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0, -12)
ctx.moveTo(0, 0)
ctx.lineTo(-12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawVerticalLineIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
const size = 24
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(-0.5, -size / 4)
ctx.lineTo(-0.5, -size / 4 + size / 2)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawHorizontalLineIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
const size = 24
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(-size / 4, -0.5)
ctx.lineTo(-size / 4 + size / 2, -0.5)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const controlPositionIcons = {
tl: drawTopLeftIcon,
t: drawHorizontalLineIcon,
tr: drawTopRightIcon,
r: drawVerticalLineIcon,
br: drawBottomRightIcon,
b: drawHorizontalLineIcon,
bl: drawBottomLeftIcon,
l: drawVerticalLineIcon
}
const ControlPositions = {
TOP_LEFT: 'tl',
TOP: 't',
TOP_RIGHT: 'tr',
RIGHT: 'r',
BOTTOM_RIGHT: 'br',
BOTTOM: 'b',
BOTTOM_LEFT: 'bl',
LEFT: 'l'
}
fabric.StaticImage = fabric.util.createClass(fabric.Image, {
type: 'StaticImage',
_editingMode: false,
__editingImage: null,
cornerLengthEditing: 5,
cornerStrokeColorEditing: 'black',
cornerSizeEditing: 2,
cropType: 'rectangle',
initialize: function (element, options) {
options || (options = {});
this.filters = [];
this.cacheKey = 'texture' + fabric.Object.__uid++;
this.registerEditingEvents();
this.callSuper('initialize', options);
this._initElement(element, options);
this.applyCrop();
},
getElement: function () {
return this._element || {};
},
applyCrop: function () {
if (this.clipPath instanceof fabric.Object) {
this.clipPath = null
}
if (this.cropType === 'circle') {
this.clipPath = new fabric.Ellipse({
rx: this.width / 2,
ry: this.height / 2,
left: -this.width / 2,
top: -this.height / 2,
});
}
if (this.cropType === 'triangle') {
this.clipPath = new fabric.Triangle({
width: this.width,
height: this.height,
left: -this.width / 2,
top: -this.height / 2,
});
}
return this
},
registerEditingEvents: function () {
this.on('mousedblclick', () => {
if (!this._editingMode) {
return this.enterEditingMode()
} else {
this.exitEditingMode()
}
})
this.on('deselected', () => {
this.exitEditingMode()
})
},
enterEditingMode: function () {
if (this.selectable && this.canvas) {
this._editingMode = true
this.__editingImage = fabric.util.object.clone(this)
this.__editingImage.clipPath = null
const element = this.__editingImage.getElement()
const { top = 0, left = 0, cropX = 0, cropY = 0, scaleX = 1, scaleY = 1 } = this.__editingImage
this.__editingImage.set({
top: top - cropY * scaleY,
left: left - cropX * scaleX,
height: element.height,
width: element.width,
cropX: 0,
cropY: 0,
opacity: 0.6,
selectable: true,
evented: false,
excludeFromExport: true
})
this.canvas.add(this.__editingImage)
this.controls = this.__editingControls()
this.canvas.requestRenderAll()
this.on('moving', this.__editingOnMoving)
}
},
exitEditingMode: function () {
if (this.selectable && this.canvas) {
this._editingMode = false
if (this.__editingImage) {
this.canvas.remove(this.__editingImage)
this.__editingImage = null
}
this.off('moving', this.__editingOnMoving)
this.controls = fabric.Object.prototype.controls
this.fire('exit:editing', { target: this })
this.canvas.requestRenderAll()
}
},
__editingControls: function () {
const controls = Object.values(ControlPositions)
return controls.map(this.__createEditingControl.bind(this))
},
__createEditingControl: function (position) {
const cursor = position
.replace('t', 's')
.replace('l', 'e')
.replace('b', 'n')
.replace('r', 'w')
return new fabric.Control({
cursorStyle: cursor + '-resize',
actionName: `edit_${this.type}`,
render: controlPositionIcons[position],
positionHandler: this.__editingControlPositionHandler.bind(this, position),
actionHandler: this.__editingActionHandlerWrapper(position)
})
},
__editingActionHandlerWrapper: function (position) {
return (_event, _transform, x, y) => {
//let localPosition = this.getLocalPointer(_event)
this.__editingSetCrop(position, x, y, true)
return true
}
},
__editingOnMoving: function (event) {
if (this._editingMode && event.pointer) {
this.set('dirty', true) // trigger cache refresh
this.__editingSetCrop(ControlPositions.TOP_LEFT, this.left, this.top)
}
},
__editingSetCrop: function (
position,
x,
y,
resize = false
) {
if (this.__editingImage) {
const { top = 0, left = 0, width = 0, height = 0, scaleX = 1, scaleY = 1 } = this.__editingImage
if (position.includes('t')) {
const maxTop = top + height * scaleY - (resize ? 0 : this.getScaledHeight())
const minTop = Math.min(y, maxTop, this.top + this.getScaledHeight())
this.top = Math.max(minTop, top)
const cropY = Math.min((Math.min(Math.max(y, top), this.top) - top) / scaleY, height)
if (resize) {
this.height = Math.max(0, Math.min(this.height + (this.cropY - cropY), height))
}
this.cropY = cropY
} else if (position.includes('b') && resize) {
const minHeight = Math.min((y - top) / scaleY - this.cropY, height - this.cropY)
this.height = Math.max(0, minHeight)
}
if (position.includes('l')) {
const maxLeft = left + width * scaleX - (resize ? 0 : this.getScaledWidth())
const minLeft = Math.min(x, maxLeft, this.left + this.getScaledWidth())
this.left = Math.max(minLeft, left)
const cropX = Math.min((Math.min(Math.max(x, left), this.left) - left) / scaleX, width)
if (resize) {
this.width = Math.max(0, Math.min(this.width + (this.cropX - cropX), width))
}
this.cropX = cropX
} else if (position.includes('r') && resize) {
const minWidth = Math.min((x - left) / scaleX - this.cropX, width - this.cropX)
this.width = Math.max(0, minWidth)
}
this.applyCrop()
}
},
__editingControlPositionHandler: function (position) {
const xMultiplier = position.includes('l') ? -1 : position.length > 1 || position === 'r' ? 1 : 0
const yMultiplier = position.includes('t') ? -1 : position.length > 1 || position === 'b' ? 1 : 0
const x = (this.width / 2) * xMultiplier
const y = (this.height / 2) * yMultiplier
return fabric.util.transformPoint(
new fabric.Point(x, y),
fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, this.calcTransformMatrix())
)
},
__renderEditingControl: function (
position,
ctx,
left,
top
) {
ctx.save()
ctx.strokeStyle = this.cornerStrokeColorEditing
ctx.lineWidth = this.cornerSizeEditing
ctx.translate(left, top)
if (this.angle) {
ctx.rotate(fabric.util.degreesToRadians(this.angle))
}
ctx.beginPath()
const x = position.includes('l') ? -ctx.lineWidth : position.includes('r') ? ctx.lineWidth : 0
const y = position.includes('t') ? -ctx.lineWidth : position.includes('b') ? ctx.lineWidth : 0
if (position === 'b' || position === 't') {
ctx.moveTo(x - this.cornerLengthEditing / 2, y)
ctx.lineTo(x + this.cornerLengthEditing, y)
} else if (position === 'r' || position === 'l') {
ctx.moveTo(x, y - this.cornerLengthEditing / 2)
ctx.lineTo(x, y + this.cornerLengthEditing)
} else {
if (position.includes('b')) {
ctx.moveTo(x, y - this.cornerLengthEditing)
} else if (position.includes('t')) {
ctx.moveTo(x, y + this.cornerLengthEditing)
}
ctx.lineTo(x, y)
if (position.includes('r')) {
ctx.lineTo(x - this.cornerLengthEditing, y)
} else if (position.includes('l')) {
ctx.lineTo(x + this.cornerLengthEditing, y)
}
}
ctx.stroke()
ctx.restore()
},
});
fabric.StaticImage.fromURL = function (url, callback, imgOptions) {
fabric.util.loadImage(url, function (img, isError) {
callback && callback(new fabric.StaticImage(img, imgOptions), isError);
}, null, imgOptions && imgOptions.crossOrigin);
};
fabric.StaticImage.fromObject = function (options, callback) {
fabric.util.loadImage(
options.src,
function (img) {
return callback && callback(new fabric.StaticImage(img, options))
},
null,
{ crossOrigin: 'anonymous' }
)
}
tried various fabric js libraries
Fabric.js 2.3.6
I'm trying to clip an object to a path drawn with the free drawing bush. The code fails to show the image inside the path and I'm not sure what I'm doing wrong.
Multiple objects could be clipped, so I can't apply the path to the canvas itself.
let image = new Image();
let object;
let canvas;
// canvas
canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
// clip
function clip(path) {
object.set({
clipTo: function(ctx) {
path.render(ctx);
}
});
canvas.requestRenderAll();
}
// image
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
top: 50,
left: 50
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
https://jsfiddle.net/o91rv38q/7/
In order to clip on the same spot when is path has been drawn, you need to reset pathOffset for a SVG path, and setTransform to the ctx. Your clip function will look like:
function clip(path) {
path.set({pathOffset: {x: 0, y: 0}});
object.set({
clipTo: function(ctx) {
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
ctx.beginPath();
path._renderPathCommands(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
_renderPathCommands - renders a path.
Updated fiddle
To clip multiple objects you'll need to have some sort of an array of objects and then combine then into single path:
function combinePaths (paths) {
if (!paths.length) {
return null;
}
let singlePath = paths[0].path;
for (let i = 1; i < paths.length; i++){
singlePath = [...singlePath, ...paths[i].path];
}
return new fabric.Path(singlePath, {
top: 0,
left: 0,
pathOffset: {
x: 0,
y: 0
}
});
}
Here is an example with multiple paths to clip:
// canvas
let canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false
});
let paths = [];
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function (options) {
paths.push(options.path);
clip(combinePaths(paths));
});
function combinePaths(paths) {
if (!paths.length) {
return null;
}
let singlePath = paths[0].path;
for (let i = 1; i < paths.length; i++) {
singlePath = [...singlePath, ...paths[i].path];
}
return new fabric.Path(singlePath, {
top: 0,
left: 0,
pathOffset: {
x: 0,
y: 0
}
});
}
function clip(path) {
if (!path) {
return;
}
object.set({
clipTo: function (ctx) {
var retina = this.canvas.getRetinaScaling();
ctx.save();
ctx.setTransform(retina, 0, 0, retina, 0, 0);
ctx.beginPath();
path._renderPathCommands(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
let object;
image.onload = function () {
object = new fabric.Image(image, {
width: 500,
height: 500,
top: 50,
left: 50
});
object.globalCompositeOperation = 'source-atop';
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<canvas id="canvas" width="1280" height="720"></canvas>
I have a canvas and have 2 images on it one is not evented and other we can change. Now I'm trying to change/replace image using file input, everything is fine just while changing the image it replaces the one I want but also adds one on top. I want to change the Image with id='changeimg'
//Add Image to Canvas
var imgObj = new Image();
//imgObj.id='changeimg';
imgObj.src = "blank.png";
var imgmain = imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.set({
id: 'changeimg',
angle: 0,
height: 350,
width: canvas.getWidth(),
align: 'mid', //added
originX: 'center', //added
originY: 'center', //added
});
canvas.centerObject(image);
canvas.add(image);
}
var imgObj1 = new Image();
//imgObj1.id='backgroundimg';
imgObj1.src = "template1.png";
var imgmain = imgObj1.onload = function() {
var image1 = new fabric.Image(imgObj1);
image1.set({
id: 'backgroundimg',
angle: 0,
height: canvas.getHeight(),
width: canvas.getWidth(),
evented: false,
});
//canvas.centerObject(image1);
canvas.add(image1);
}
//On Image Browse and Set on Canvas
document.getElementById('uploadedImg').onchange = function handleImage(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(file) {
addImage(file.target.result);
}
reader.readAsDataURL(file);
}
function addImage(imgLink) {
fabric.Image.fromURL(imgLink, function(img) {
img.set({
'left': 50
});
img.set({
'top': 150
});
img.scaleToWidth(250);
img.scaleToHeight(250);
var cnt = 0;
var objs = canvas.getObjects();
//var objs = canvas.getActiveObject();
if (objs.length > 0) {
objs.forEach(function(e) {
if (e && e.type === 'image' && e.id === "changeimg") {
e._element.src = imgLink;;
canvas.renderAll();
cnt = 1;
}
});
}
});
}
Use imageEl.setSrc to change source of image element.
DEMO
var canvas = new fabric.Canvas('c');
fabric.Image.fromURL("https://picsum.photos/200/300/?random", function(img) {
img.set({
id: 'changeimg',
align: 'mid', //added
originX: 'center', //added
originY: 'center', //added,
scaleX: 200 / img.width,
scaleY: 200 / img.height,
});
canvas.centerObject(img);
canvas.add(img);
image = img;
})
fabric.Image.fromURL("https://picsum.photos/200/250/?random", function(img) {
img.set({
id: 'backgroundimg',
angle: 0,
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
});
//canvas.centerObject(image1);
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
})
//On Image Browse and Set on Canvas
document.getElementById('uploadedImg').onchange = function handleImage(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(file) {
addImage(file.target.result);
}
reader.readAsDataURL(file);
}
function addImage(imgLink) {
fabric.Image.fromURL(imgLink, function(img) {
var objects = canvas.getObjects();
for (var i = 0, l = objects.length; i < l; i++) {
if (objects[i].id == 'changeimg') {
imageEl = objects[i];
break
}
}
if (imageEl) {
imageEl.setSrc(img.getSrc(), function() {
canvas.renderAll()
imageEl.setCoords();
},{
left: 50,
top: 150,
scaleX: 150 / img.width,
scaleY: 200 / img.height,
})
}
});
}
canvas{
border:2px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.4/fabric.js"></script>
<canvas id="c" width=500 height=500></canvas><br>
<input type='file' id='uploadedImg'>
How to change attribute name which is dropped on another paper?
This is my markup and code:
<!DOCTYPE html>
<html>
<head>`enter code here`
<link href="css/joint.min.css" rel="stylesheet" type="text/css"/>
<link href="css/joint.css" rel="stylesheet" type="text/css"/>
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/lodash.js" type="text/javascript"></script>
<script src="js/backbone.js" type="text/javascript"></script>
<script src="js/joint.js" type="text/javascript"></script>
<script src="js/joint.min.js" type="text/javascript"></script>
<script src="js/circle-constraint.js" type="text/javascript"></script>
<link href="css/cascadeTest.css" rel="stylesheet" type="text/css"/>
<script src="js/inspector.js" type="text/javascript"></script>
</head>
<body>
<div id="stencil"></div>
<div id="paper"></div>
<div class="inspector-container"></div>
<script>
// Canvas where sape are dropped
var graph = new joint.dia.Graph;
paper = new joint.dia.Paper({
el: $('#paper'),
model: graph
});
// Canvas from which you take shapes
var stencilGraph = new joint.dia.Graph;
stencilPaper = new joint.dia.Paper({
el: $('#stencil'),
height: 60,
model: stencilGraph,
interactive: false
});
var r1 = new joint.shapes.basic.Rect({
position: {
x: 10,
y: 10
},
size: {
width: 100,
height: 40
},
attrs: {
text: {
text: 'Rect1'
}
}
});
var r2 = new joint.shapes.basic.Rect({
position: {
x: 120,
y: 10
},
size: {
width: 100,
height: 40
},
attrs: {
text: {
text: 'Rect2'
}
}
});
var erd = joint.shapes.erd;
erd.ISA.prototype.getHighlighterPath = function (w, h) {
return ['M', -8, 1, w + 8, 1, w / 2, h + 2, 'z'].join(' ');
};
var isa = new erd.ISA({
position: {x: 400, y: 10},
attrs: {
text: {
text: 'ISA',
fill: '#ffffff',
'letter-spacing': 0,
style: {'text-shadow': '1px 0 1px #333333'}
},
polygon: {
fill: '#fdb664',
stroke: 'none',
filter: {name: 'dropShadow', args: {dx: 0, dy: 2, blur: 1, color: '#333333'}}
}
}
});
var earth = new joint.shapes.basic.Circle({
position: {x: 250, y: 10},
size: {width: 100, height: 40},
attrs: {text: {text: 'circle'}, circle: {fill: '#2ECC71'}},
name: 'earth'
});
var image = new joint.shapes.basic.Image({
position: {
x: 500,
y: 10
},
size: {
width: 80,
height: 40
},
attrs: {
image: {
"xlink:href": "download.jpg",
width: 80,
height: 40
}
}
});
stencilGraph.addCells([r1, r2, earth, isa, image]);
stencilPaper.on('cell:pointerdown', function(cellView, e, x, y) {
$('body').append('<div id="flyPaper" style="position:fixed;z-index:100;opacity:.7;pointer-event:none;"></div>');
var flyGraph = new joint.dia.Graph,
flyPaper = new joint.dia.Paper({
el: $('#flyPaper'),
model: flyGraph,
interactive: false
}),
flyShape = cellView.model.clone(),
pos = cellView.model.position(),
offset = {
x: x - pos.x,
y: y - pos.y
};
flyShape.position(0, 0);
flyGraph.addCell(flyShape);
$("#flyPaper").offset({
left: e.pageX - offset.x,
top: e.pageY - offset.y
});
$('body').on('mousemove.fly', function(e) {
$("#flyPaper").offset({
left: e.pageX - offset.x,
top: e.pageY - offset.y
});
});
$('body').on('mouseup.fly', function(e) {
var x = e.pageX,
y = e.pageY,
target = paper.$el.offset();
// Dropped over paper ?
if (x > target.left && x < target.left + paper.$el.width() && y > target.top && y < target.top + paper.$el.height()) {
var s = flyShape.clone();
s.position(x - target.left - offset.x, y - target.top - offset.y);
graph.addCell(s);
}
$('body').off('mousemove.fly').off('mouseup.fly');
flyShape.remove();
$('#flyPaper').remove();
});
});
</script>
</body>
</html>
once the element is dropped you are having it in
graph.addCell(s);
s.attr('attributeName/text',newValue)
assuming the new attribute has property text.