| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- function mxFreehand(graph)
- {
- // Graph must have a container
- var svgElement = (graph.view != null && graph.view.canvas != null) ? graph.view.canvas.ownerSVGElement : null;
-
- if (graph.container == null || svgElement == null)
- {
- return;
- }
-
- // Stops drawing on escape
- graph.addListener(mxEvent.ESCAPE, mxUtils.bind(this, function()
- {
- this.stopDrawing();
- }));
-
- //Code inspired by https://stackoverflow.com/questions/40324313/svg-smooth-freehand-drawing
- var bufferSize = mxFreehand.prototype.MILD_SMOOTHING;
- var path = null;
- var partPathes = [];
- var strPath;
- var drawPoints = [];
- var lastPart;
- var closedPath = false;
- var autoClose = true;
- var autoInsert = true;
- var autoScroll = true;
- var openFill = true;
- var buffer = []; // Contains the last positions of the mouse cursor
- var enabled = false;
- var stopClickEnabled = false;
- var selectInserted = false;
- var currentStrokeColor = 'default';
- var perfectFreehandOptions = {
- size: 5,
- thinning: 0.5,
- smoothing: 0.5,
- streamline: 0.5,
- // easing: (t) => t,
- start: {
- taper: 0,
- // easing: (t) => t,
- cap: true
- },
- end: {
- taper: 0,
- // easing: (t) => t,
- cap: true
- }
- };
- var perfectFreehandMode = true;
- this.setClosedPath = function(isClosed)//TODO add closed settings
- {
- closedPath = isClosed;
- };
-
- this.setAutoClose = function(isAutoClose)//TODO add auto closed settings
- {
- autoClose = isAutoClose;
- };
- this.setAutoInsert = function(value)
- {
- autoInsert = value;
- };
-
- this.setAutoScroll = function(value)
- {
- autoScroll = value;
- };
-
- this.setOpenFill = function(value)
- {
- openFill = value;
- };
-
- this.setStopClickEnabled = function(enabled)
- {
- stopClickEnabled = enabled;
- };
- this.setSelectInserted = function(value)
- {
- selectInserted = value;
- };
- this.setSmoothing = function(smoothing)
- {
- bufferSize = smoothing;
- };
- this.getSmoothing = function()
- {
- return bufferSize;
- };
-
- this.setPerfectFreehandMode = function(value)
- {
- perfectFreehandMode = value;
- };
- this.isPerfectFreehandMode = function()
- {
- return perfectFreehandMode;
- };
- this.setBrushSize = function(value)
- {
- perfectFreehandOptions.size = value;
- };
- this.getBrushSize = function()
- {
- return perfectFreehandOptions.size;
- };
- var setEnabled = function(isEnabled)
- {
- enabled = isEnabled;
- graph.getRubberband().setEnabled(!isEnabled);
- graph.graphHandler.setSelectEnabled(!isEnabled);
- graph.graphHandler.setMoveEnabled(!isEnabled);
- graph.container.style.cursor = (isEnabled) ? 'crosshair' : '';
- graph.fireEvent(new mxEventObject('freehandStateChanged'));
- };
-
- this.startDrawing = function()
- {
- setEnabled(true);
- }
-
- this.isDrawing = function()
- {
- return enabled;
- };
-
- var endPath = mxUtils.bind(this, function(e)
- {
- if (path)
- {
- var lastLength = lastPart.length;
-
- // Click stops drawing
- var doStop = stopClickEnabled && drawPoints.length > 0 &&
- lastPart != null && lastPart.length < 2;
-
- if (!doStop)
- {
- drawPoints.push.apply(drawPoints, lastPart);
- }
-
- lastPart = [];
- drawPoints.push(null);
- partPathes.push(path);
- path = null;
-
- if (doStop || autoInsert)
- {
- this.stopDrawing();
- }
-
- if (autoInsert && (!doStop || lastLength >= 2))
- {
- this.startDrawing();
- }
-
- mxEvent.consume(e);
- }
- });
- // Used to retrieve default styles
- var edge = new mxCell();
- edge.edge = true;
- this.getStrokeColor = function(allowDefault)
- {
- var strokeColor = (currentStrokeColor != null) ? currentStrokeColor :
- mxUtils.getValue(graph.currentVertexStyle, mxConstants.STYLE_STROKECOLOR,
- mxUtils.getValue(graph.getCurrentCellStyle(edge),
- mxConstants.STYLE_STROKECOLOR, '#000'))
- if (strokeColor == 'default' && !allowDefault)
- {
- strokeColor = graph.shapeForegroundColor;
- }
- return strokeColor;
- };
- this.setStrokeColor = function(value)
- {
- currentStrokeColor = value;
- };
- this.createStyle = function(stencil)
- {
- var style = ';fillColor=none;';
- if (perfectFreehandMode)
- {
- style = ';lineShape=1;';
- }
- style += 'strokeColor=' + this.getStrokeColor(true) + ';';
- return mxConstants.STYLE_SHAPE + '=' + stencil + style;
- };
-
- this.stopDrawing = function()
- {
- if (partPathes.length > 0)
- {
- if (perfectFreehandMode)
- {
- var tmpPoints = [];
-
- for (var i = 0; i < drawPoints.length; i++)
- {
- if (drawPoints[i] != null)
- {
- tmpPoints.push([drawPoints[i].x, drawPoints[i].y]);
- }
- }
- var output = PerfectFreehand.getStroke(tmpPoints, perfectFreehandOptions);
- drawPoints = [];
-
- for (var i = 0; i < output.length; i++)
- {
- drawPoints.push({x: output[i][0], y: output[i][1]});
- }
-
- drawPoints.push(null);
- }
- var maxX = drawPoints[0].x, minX = drawPoints[0].x, maxY = drawPoints[0].y, minY = drawPoints[0].y;
-
- for (var i = 1; i < drawPoints.length; i++)
- {
- if (drawPoints[i] == null) continue;
-
- maxX = Math.max(maxX, drawPoints[i].x);
- minX = Math.min(minX, drawPoints[i].x);
- maxY = Math.max(maxY, drawPoints[i].y);
- minY = Math.min(minY, drawPoints[i].y);
- }
-
- var w = maxX - minX, h = maxY - minY;
- if (w > 0 && h > 0)
- {
- var xScale = 100 / w;
- var yScale = 100 / h;
-
- drawPoints.map(function(p)
- {
- if (p == null) return p;
-
- p.x = (p.x - minX) * xScale;
- p.y = (p.y - minY) * yScale;
- return p;
- });
-
- //toFixed(2) to reduce size of output
- var drawShape = '<shape strokewidth="inherit"><foreground>';
-
- var start = 0;
-
- for (var i = 0; i < drawPoints.length; i++)
- {
- var p = drawPoints[i];
- if (p == null)
- {
- var tmpClosedPath = false;
- var startP = drawPoints[start], endP = drawPoints[i - 1];
-
- if (!closedPath && autoClose)
- {
- var xdiff = startP.x - endP.x, ydiff = startP.y - endP.y;
- var startEndDist = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
-
- tmpClosedPath = startEndDist <= graph.tolerance;
- }
-
- if (closedPath || tmpClosedPath)
- {
- drawShape += '<line x="'+ startP.x.toFixed(2) + '" y="' + startP.y.toFixed(2) + '"/>';
- }
-
- drawShape += '</path>' + ((openFill || closedPath || tmpClosedPath)? '<fillstroke/>' : '<stroke/>');
- start = i + 1;
- }
- else if (i == start)
- {
- drawShape += '<path><move x="'+ p.x.toFixed(2) + '" y="' + p.y.toFixed(2) + '"/>'
- }
- else
- {
- drawShape += '<line x="'+ p.x.toFixed(2) + '" y="' + p.y.toFixed(2) + '"/>';
- }
- }
-
- drawShape += '</foreground></shape>';
- if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
- {
- var style = this.createStyle('stencil(' + Graph.compress(drawShape) + ')');
- var s = graph.view.scale;
- var tr = graph.view.translate;
-
- var cell = new mxCell('', new mxGeometry(minX / s - tr.x, minY / s - tr.y, w / s, h / s), style);
- cell.vertex = 1;
-
- graph.model.beginUpdate();
- try
- {
- cell = graph.addCell(cell);
-
- graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [cell]));
- graph.fireEvent(new mxEventObject('freehandInserted', 'cell', cell));
- }
- finally
- {
- graph.model.endUpdate();
- }
-
- if (selectInserted)
- {
- graph.setSelectionCells([cell]);
- }
- }
- }
- for (var i = 0; i < partPathes.length; i++)
- {
- partPathes[i].parentNode.removeChild(partPathes[i]);
- }
-
- path = null;
- partPathes = [];
- drawPoints = [];
- }
- setEnabled(false);
- };
- // Stops all interactions if freehand is enabled
- graph.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
- {
- var evtName = evt.getProperty('eventName');
- var me = evt.getProperty('event');
-
- if (evtName == mxEvent.MOUSE_MOVE && enabled)
- {
- if (me.sourceState != null)
- {
- me.sourceState.setCursor('crosshair');
- }
-
- me.consume();
- }
- }));
-
- // Implements a listener for hover and click handling
- graph.addMouseListener(
- {
- mouseDown: mxUtils.bind(this, function(sender, me)
- {
- if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
- {
- var e = me.getEvent();
-
- if (!enabled || mxEvent.isPopupTrigger(e) ||
- mxEvent.isMiddleMouseButton(e) ||
- mxEvent.isMultiTouchEvent(e))
- {
- return;
- }
-
- var strokeWidth = parseFloat(graph.currentVertexStyle[mxConstants.STYLE_STROKEWIDTH] || 1);
- strokeWidth = Math.max(1, strokeWidth * graph.view.scale);
- var strokeColor = this.getStrokeColor();
- path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- path.setAttribute('fill', perfectFreehandMode? strokeColor : 'none');
- path.setAttribute('pointer-events', 'none');
- path.setAttribute('stroke', strokeColor);
- path.setAttribute('stroke-width', strokeWidth);
-
- if (graph.currentVertexStyle[mxConstants.STYLE_DASHED] == '1')
- {
- var dashPattern = graph.currentVertexStyle[mxConstants.STYLE_DASH_PATTERN] || '3 3';
-
- dashPattern = dashPattern.split(' ').map(function(p)
- {
- return parseFloat(p) * strokeWidth;
- }).join(' ');
- path.setAttribute('stroke-dasharray', dashPattern);
- }
-
- buffer = [];
- var pt = getMousePosition(e);
- appendToBuffer(pt);
- strPath = 'M' + pt.x + ' ' + pt.y;
- drawPoints.push(pt);
- lastPart = [];
- path.setAttribute('d', perfectFreehandMode?
- PerfectFreehand.getSvgPathFromStroke([[pt.x, pt.y]], perfectFreehandOptions)
- : strPath);
- svgElement.appendChild(path);
-
- me.consume();
- }
- }),
- mouseMove: mxUtils.bind(this, function(sender, me)
- {
- if (path != null && graph.isEnabled() &&
- !graph.isCellLocked(graph.getDefaultParent()))
- {
- var e = me.getEvent();
- var pt = getMousePosition(e);
- appendToBuffer(pt);
- updateSvgPath();
-
- if (autoScroll)
- {
- var tr = graph.view.translate;
- graph.scrollRectToVisible(new mxRectangle(pt.x - tr.x, pt.y - tr.y).grow(20));
- }
-
- me.consume();
- }
- }),
- mouseUp: mxUtils.bind(this, function(sender, me)
- {
- if (path != null && graph.isEnabled() &&
- !graph.isCellLocked(graph.getDefaultParent()))
- {
- endPath(me.getEvent());
- me.consume();
- }
- })
- });
- var getMousePosition = function (e)
- {
- return mxUtils.convertPoint(graph.container, mxEvent.getClientX(e), mxEvent.getClientY(e));
- };
- var appendToBuffer = function (pt)
- {
- buffer.push(pt);
-
- while (buffer.length > bufferSize)
- {
- buffer.shift();
- }
- };
- // Calculate the average point, starting at offset in the buffer
- var getAveragePoint = function (offset)
- {
- var len = buffer.length;
-
- if (len % 2 === 1 || len >= bufferSize)
- {
- var totalX = 0;
- var totalY = 0;
- var pt, i;
- var count = 0;
-
- for (i = offset; i < len; i++)
- {
- count++;
- pt = buffer[i];
- totalX += pt.x;
- totalY += pt.y;
- }
-
- return {
- x: totalX / count,
- y: totalY / count
- }
- }
-
- return null;
- };
- var updateSvgPath = function ()
- {
- var pt = getAveragePoint(0);
- if (pt)
- {
- drawPoints.push(pt);
- if (perfectFreehandMode)
- {
- var tmpPoints = [];
- for (var i = 0; i < drawPoints.length; i++)
- {
- tmpPoints.push([drawPoints[i].x, drawPoints[i].y]);
- }
- lastPart = [];
-
- for (var offset = 2; offset < buffer.length; offset += 2)
- {
- pt = getAveragePoint(offset);
- tmpPoints.push([pt.x, pt.y]);
- lastPart.push(pt);
- }
- path.setAttribute('d', PerfectFreehand.getSvgPathFromStroke(tmpPoints, perfectFreehandOptions));
- }
- else
- {
- // Get the smoothed part of the path that will not change
- strPath += ' L' + pt.x + ' ' + pt.y;
- // Get the last part of the path (close to the current mouse position)
- // This part will change if the mouse moves again
- var tmpPath = '';
- lastPart = [];
-
- for (var offset = 2; offset < buffer.length; offset += 2)
- {
- pt = getAveragePoint(offset);
- tmpPath += ' L' + pt.x + ' ' + pt.y;
- lastPart.push(pt);
- }
- // Set the complete current path coordinates
- path.setAttribute('d', strPath + tmpPath);
- }
- }
- };
- };
- mxFreehand.prototype.NO_SMOOTHING = 1;
- mxFreehand.prototype.MILD_SMOOTHING = 4;
- mxFreehand.prototype.NORMAL_SMOOTHING = 8;
- mxFreehand.prototype.VERY_SMOOTH_SMOOTHING = 12;
- mxFreehand.prototype.SUPER_SMOOTH_SMOOTHING = 16;
- mxFreehand.prototype.HYPER_SMOOTH_SMOOTHING = 20;
|