janvas实现xmind可编辑思维导图效果代码
代码语言:html
所属分类:图表
代码描述:janvas实现xmind可编辑思维导图效果代码影效果代码图饼状图图表效果代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <style> html, body { margin: 0; padding: 0; width: 100%; height: 100%; } </style> </head> <body > <div id="app" style="width: 100%;height: 100%;"></div> <script type="module"> /** * XMind software * Created by jarenchow based on janvas * https://github.com/jarenchow/jmind * https://github.com/jarenchow/janvas * https://github.com/jarenchow/janvasexamples */ import janvas from "https://cdn.skypack.dev/janvas@2.7.4"; var xmind = new janvas.Canvas({ container: "#app", props: { data: { // TODO: node 添加 inverse 属性,使得可以向左布局 value: "Central Topic", children: [ { value: "Main Topic 1", children: [ {value: "Subtopic 1", children: []}, {value: "Subtopic 2", children: []} ] }, { value: "Main Topic 2", children: [ {value: "Subtopic 1", children: []} ] }, { value: "Main Topic 3", collapse: true, children: [ {value: "Subtopic 1", children: []}, { value: "Subtopic 2", children: [ {value: "Subtopic", children: []}, {value: "Subtopic", children: []} ] }, {value: "Subtopic 3", children: []} ] } ] }, style: { backgroundColor: "#ecf4f9", boxBackgroundColor: "#35bffe19", boxBorderColor: "#28beff3d", centralSpacing: 50, spacing: 26, centralVerticalSpacing: 35, verticalSpacing: 8, centralTopic: { backgroundColor: "#35455b", color: "#ffffff", fontSize: 24, paddingLeft: 30, paddingTop: 22, paddingBottom: 18 }, mainTopic: { backgroundColor: "#84a1c9", color: "#ffffff", fontSize: 18, paddingLeft: 18, paddingTop: 14, paddingBottom: 12 }, subTopic: { backgroundColor: "#e2e9f1", color: "#35455b", fontSize: 14, paddingLeft: 7, paddingTop: 9, paddingBottom: 6 }, centralLink: { backgroundColor: "#ecf4f9", backgroundMousein: "#d2dbe2", color: "#35455b", lineWidth: 3, spacing: 13, arcToRadius: 8, arcRadius: 6, arcRadiusCollapse: 8, arcLineLength: 8, arcLineWidth: 1, font: "10px Open Sans" }, mainLink: { lineWidth: 2 } }, point: { _locate: new janvas.Point(0, 0), _offset: new janvas.Point(0, 0), _before: new janvas.Point(0, 0), _delta: new janvas.Point(0, 0), _conflict: new janvas.Point(0, 0), locate: function (x, y) { this._locate.init(x, y); }, set: function (x, y) { this._offset.init(x, y); this._onOffsetChanged(); }, add: function (x, y) { this._offset.translate(x, y); this._onOffsetChanged(); }, delta: function (x, y) { this._delta.init(x, y); }, isDelta: function () { return this._delta.x || this._delta.y; }, beforeUpdate: function () { this._before.copy(this._offset); }, update: function (ratio) { this.set(this._before.x + this._delta.x * this._ease(ratio), this._before.y + this._delta.y * this._ease(ratio)); }, afterUpdate: function () { this.beforeUpdate(); }, _ease: janvas.Utils.ease.out.quad, eventdown: function () { this._conflict.init(0, 0); if (!this.$isRunning) this.beforeUpdate(); }, eventmove: function (moveX, moveY) { if (this.$isRunning) this._conflict.init(moveX, moveY); else this.set(this._before.x + moveX - this._conflict.x, this._before.y + moveY - this._conflict.y); }, _onOffsetChanged: function () { this.onOffsetChanged(this._locate.x + this._offset.x, this._locate.y + this._offset.y); }, onOffsetChanged: janvas.Utils.noop }, mouse: { type: 0, none: 0, left: 1, right: 2, center: 4, back: 8, front: 16 }, format: { markdown: { prefix: "- ", suffix: "\n", indent: 2, separate: " ", regex: /\r\n(?= )/ }, xmind: { prefix: "", suffix: "\n", indent: 1, separate: "\t", regex: /\r\n(?=\t)/ } }, topic: function (depth) { return depth ? "Subtopic" + " " : "Main Topic" + " "; } }, components: { Node: (function () { Node._serialId = 0; Node.OPERATION = { APPENDCHILD: "_appendOperation", REMOVECHILD: "_removeOperation", SETVALUE: "_setValueOperation", COLLAPSE: "_collapseOperation" }; Node.setOnOperation = function (onOperation) { this.prototype.onOperation = onOperation; }; function Node($ctx) { this._serialId = Node._serialId++; this.$ctx = $ctx; this.x = this.y = this.width = this.height = this.borderX = this.borderY = 0; this.parent = null; this.children = []; this.index = 0; // 所处 children 中的 index this.depth = 0; // 所处树的深度 this.value = ""; this.values = []; this.background = new janvas.RoundRect(this.$ctx, 0, 0, 0, 0, 0); this.border = new janvas.RoundRect(this.$ctx, 0, 0, 0, 0, 0); this.texts = []; this.isMousein = false; this.isSelected = false; this.style = new Node.Style(this); this.link = new Node.Link(this); // this.collapse = false; this.count = 0; this.length = 0; // 全等于 this.children.length } Node.prototype = { reset: function () { this.parent = null; this.children.length = 0; this.length = 0; this.index = 0; this.depth = 0; this.count = 0; this.isMousein = false; this.isSelected = false; // this.collapse = false; }, apply: function () { // TODO: 整体 x, y, width, height 属性取整 var style = this.style, x = this.x, y = this.y, borderX = x - style.borderOffset, borderY = y - style.borderOffset; this.background.initXY(x, y); this.border.initXY(borderX, borderY); this.borderX = borderX - style._borderOffset; this.borderY = borderY - style._borderOffset; for (var i = 0, length = this.values.length; i < length; i++) { this.texts[i].initXY(x + style.paddingLeft, y + style.paddingTop + i * style.lineHeight); } }, collidesWith: function (rect) { return janvas.Collision.rect(this.x, this.y, this.right, this.bottom, rect._left, rect._top, rect._right, rect._bottom); }, draw: function () { this.background.fill(); if (this.isMousein || this.isSelected) this.border.stroke(); for (var i = 0, length = this.values.length; i < length; i++) this.texts[i].fill(); }, appendChild: function (child) { this.insertChild(child, this.length); }, insertChild: function (child, index) { this._appendOperation(child, index); this.onOperation(this, child, Node.OPERATION.APPENDCHILD, index, Node.OPERATION.REMOVECHILD, null); }, _appendOperation: function (child, index) { child.parent = this; child.index = index; index ? index < this.length ? this.children.splice(index, 0, child) : this.children.push(child) : this.children.unshift(child); for (var i = this.length++; i > index;) this.children[i--].index++; this._appendDepth(child, this.depth + 1); this._appendCount(child, child.count + 1); }, _appendDepth: function (node, depth) { node.depth = depth; node.forEachChild(function (child) { this._appendDepth(child, depth + 1); }, this); }, _appendCount: function (node, count) { while ((node = node.parent)) node.count += count; }, removeChild: function (child) { this._removeOperation(child); this.onOperation(this, child, Node.OPERATION.REMOVECHILD, null, Node.OPERATION.APPENDCHILD, child.index); }, _removeOperation: function (child) { for (var i = --this.length; i > child.index;) this.children[i--].index--; child.index ? child.index < this.length ? this.children.splice(child.index, 1) : this.children.pop() : this.children.shift(); this._appendCount(child, -(child.count + 1)); }, forEachChild: function (callback, context, start, end) { if (start === void (0)) start = 0; if (end === void (0)) end = this.length; var step = end > start ? 1 : -1; while (start !== end) { callback.call(context, this.children[start]); start += step; } }, getValue: function () { return this.value; }, setValue: function (value) { this._setValueOperation(value); }, _setValueOperation: function (value) { this.value = value; this._apply(); }, _apply: function () { var value = this.value, values = this.values, length, texts = this.texts, style = this.style, width = 0, maxWidth = 0, cursor = 0, i, c, w; values.length = 0; for (i = 0, length = value.length; i < length; i++) { c = value[i]; if (c === "\n") { values.push(value.substring(cursor, i)); cursor = i + 1; if (width > maxWidth) maxWidth = width; width = 0; } else { w = janvas.Utils.measureTextWidth(c, style.font); if ((width += w) > style.maxWidth) { values.push(value.substring(cursor, i)); cursor = i; maxWidth = style.maxWidth; width = w; } } } values.push(value.substring(cursor)); length = values.length; while (texts.length < length) { var text = new janvas.Text(this.$ctx, 0, 0); text.getStyle().setFillStyle(style.color) .setFont(style.font).setTextBaseline("top"); texts.push(text); } this.background .setWidth(this.width = style.paddingLeft + style.paddingRight + (width > maxWidth ? width : maxWidth)) .setHeight(this.height = style.paddingTop + style.paddingBottom + style.fontSize + (length - 1) * style.lineHeight); this.border .setWidth(this.width + style.borderOffset * 2) .setHeight(this.height + style.borderOffset * 2); for (i = 0; i < length; i++) { texts[i].setText(values[i]); } this.halfWidth = this.width / 2; this.halfHeight = this.height / 2; this.layoutX += 0; this.layoutY += 0; }, eventmove: function (x, y) { return this.isMousein = this.background.isPointInPath(x, y); }, select: function () { this.isSelected = true; this.border.getStyle().setStrokeStyle(this.style.borderColor); }, unselect: function () { this.isSelected = this.isMousein = false; this.border.getStyle().setStrokeStyle(this.style.borderAlpha); }, hide: function () { this.draw = janvas.Utils.noop; }, show: function () { this.draw = Node.prototype.draw; }, onValueOperation: function (_value) { this.onOperation(this, null, Node.OPERATION.SETVALUE, this.value, Node.OPERATION.SETVALUE, _value); }, beforeOffset: function () { this._x = this.x; this._y = this.y; }, offset: function (offsetX, offsetY) { this.layoutX = this._x + offsetX; this.layoutY = this._y + offsetY; }, get layoutX() { return this.x; }, set layoutX(x) { this.x = x; this.right = x + this.width; this.cx = x + this.halfWidth; }, get layoutY() { return this.y; }, set layoutY(y) { this.y = y; this.bottom = y + this.height; this.cy = y + this.halfHeight; }, get collapse() { return this._collapse; }, set collapse(collapse) { var _collapse = this._collapse; this._collapseOperation(collapse); // 须不等判断,否则会造成回收机制 collapse 赋同值 if (_collapse !== void (0) && _collapse !== collapse) this.onOperation(this, null, Node.OPERATION.COLLAPSE, collapse, Node.OPERATION.COLLAPSE, _collapse); }, _collapseOperation: function (collapse) { this.link._collapse(this._collapse = collapse); }, get count() { return this._count; }, set count(count) { this.link._count(this._count = count); }, get length() { return this._length; }, set length(length) { this.link._length(this._length = length); }, get first() { return this.children[0]; }, get last() { return this.children[this.length - 1]; }, get previous() { return this.parent.children[this.index - 1]; }, get next() { return this.parent.children[this.index + 1]; }, onOperation: janvas.Utils.noop }; Node.Style = function (target) { this.target = target; } Node.Style.prototype = { apply: function (style) { this.backgroundColor = style.backgroundColor || "#e2e9f1"; this.color = style.color || "#35455b"; this.fontSize = style.fontSize || 14; this.fontFamily = "Open Sans"; this.font = this.fontSize + "px " + this.fontFamily; this.lineHeight = Math.floor(this.fontSize * 4 / 3); this.maxWidth = 280; this.paddingLeft = style.paddingLeft || this.fontSize * 0.625; this.paddingRight = style.paddingRight || this.paddingLeft; this.paddingTop = style.paddingTop || this.fontSize * 0.5; this.paddingBottom = style.paddingBottom || this.paddingTop; this.borderRadius = this.fontSize * 0.3; this.borderPadding = 2; this.borderWidth = 2; this._borderOffset = this.borderWidth / 2; this.borderOffset = this.borderPadding + this._borderOffset; this.borderColor = "#2ebdff"; this.borderAlpha = this.borderColor + "80"; this._apply(); }, _apply: function () { var target = this.target, background = target.background, border = target.border, texts = target.texts; background.setRadius(this.borderRadius).getStyle() .setFillStyle(this.backgroundColor).setLineWidth(this.borderWidth); border.setRadius(this.borderRadius).getStyle() .setStrokeStyle(target.isSelected ? this.borderColor : this.borderAlpha) .setLineWidth(this.borderWidth); for (var i = 0, length = texts.length; i < length; i++) { texts[i].getStyle().setFillStyle(this.color).setFont(this.font); } target._apply(); } }; Node.Link = function (target) { this.target = target; this._link = new _Link(target.$ctx, 0, 0, target.children, 0, 0); this.arc = new janvas.Arc(target.$ctx, 0, 0, 0); this.text = new janvas.Text(target.$ctx, 0, 0, ""); this.line = new janvas.Line(target.$ctx, 0, 0, 0, 0); this.style = new Node.Link.Style(this); this.isMousein = false; } Node.Link.prototype = { apply: function () { var target = this.target, style = this.style, arc = this.arc; this._link.initXY(target.right, target.cy); arc.initXY(target.right + style.spacing, target.cy); this.text.initXY(arc.getStartX(), target.cy); this.line.initXY(arc.getStartX() - style.arcLineLength / 2, target.cy) .setEndX(arc.getStartX() + style.arcLineLength / 2).setEndY(target.cy); if (target.length) { this._left = target.right; if (target.collapse) { this._top = target.cy - arc.getRadius(); this._right = arc.getStartX() + arc.getRadius(); this._bottom = target.cy + arc.getRadius(); } else { this._top = target.first.y; this._right = target.first.x; this._bottom = target.last.bottom; } this.collidesWith = this._collidesWith; } else { this.collidesWith = this._noCollision; } }, draw: function () { this._link.stroke(); if (this.target.collapse) { this.arc.fillStroke(); this.text.fill(); } else if (this.target.isMousein || this.isMousein) { this.arc.fillStroke(); this.line.stroke(); } }, eventdown: function () { this.target.collapse = !this.target.collapse; }, eventmove: function (x, y) { if (this.target.length === 0) return false; if (this.isMousein !== (this.target.collapse ? this.arc.isPointInPath(x, y) : x >= this._left && x <= this._right && y >= this.target.y && y <= this.target.bottom)) { this.arc.getStyle().setFillStyle((this.isMousein = !this.isMousein) ? this.style.backgroundMousein : this.style.backgroundColor); } return this.isMousein; }, _collidesWith: function (rect) { return janvas.Collision.rect(this._left, this._top, this._right, this._bottom, rect._left, rect._top, rect._right, rect._bottom); }, _noCollision: function () { return false; }, _count: function (count) { this.text.setText(count > 99 ? "..." : count + ""); }, _collapse: function (collapse) { this._link._collapse = collapse; this.arc.setRadius(collapse ? this.style.arcRadiusCollapse : this.style.arcRadius); }, _length: function (length) { this._link._length = length; } }; Node.Link.Style = function (target) { this.target = target; } Node.Link.Style.prototype = { apply: function (style) { this.backgroundColor = style.backgroundColor || "#ecf4f9"; this.backgroundMousein = style.backgroundMousein || "#d2dbe2"; this.color = style.color || "#35455b"; this.lineWidth = style.lineWidth || 2; this.spacing = style.spacing || 13; this.arcToRadius = style.arcToRadius || 8; this.arcRadius = style.arcRadius || 6; this.arcRadiusCollapse = style.arcRadiusCollapse || 8; this.arcLineLength = style.arcLineLength || this.arcRadius * 4 / 3; this.arcLineWidth = style.arcLineWidth || 1; this.font = style.font || "10px Open Sans"; this._apply(); }, _apply: function () { var target = this.target, _link = target._link; _link.setSpacing(this.spacing); _link.setArcToRadius(this.arcToRadius); _link.getStyle().setStrokeStyle(this.color).setLineWidth(this.lineWidth); target.arc.setRadius(_link._collapse ? this.arcRadiusCollapse : this.arcRadius) .getStyle().setLineWidth(this.arcLineWidth).setStrokeStyle(this.color) .setFillStyle(target.isMousein ? this.backgroundMousein : this.backgroundColor); target.text.getStyle().setFont(this.font).setFillStyle(this.color) .setTextAlign("center").setTextBaseline("middle"); target.line.getStyle().setLineWidth(this.arcLineWidth) .setStrokeStyle(this.color); } }; function _Link(ctx, sx, sy, children, cx, cy) { janvas.Shape.call(this, ctx, sx, sy, cx, cy); this.children = children; } janvas.Utils.inheritPrototype(_Link, janvas.Shape); Object.defineProperties(_Link.prototype, { process: { value: function () { var ctx = this.ctx, tx = this._sx + this.spacing, ty = this._sy, children = this.children, i, child, ex, ey; ctx.beginPath(); ctx.moveTo(this._sx, ty); ctx.lineTo(tx, ty); if (this._collapse) return; for (i = 0; i < this._length; i++) { child = children[i]; ex = child.x - this.cx; ey = child.cy - this.cy; if (Math.abs(ey - ty) < this.arcToRadius) { ctx.moveTo(tx, ey); ctx.lineTo(ex, ey); } else { ctx.moveTo(tx, ty); // 可以 if (i),因为第一次已经 lineTo(tx, ty) 了 ctx.arcTo(tx, ey, ex, ey, this.arcToRadius); ctx.lineTo(ex, ey); // 因为 (ex - tx) > this.arcToRadius === true } } } }, setSpacing: { value: function (spacing) { this.spacing = spacing; } }, setArcToRadius: { value: function (arcToRadius) { this.arcToRadius = arcToRadius; } } }); return Node; }()), Selector: (function () { function Selector() { this.selected = []; this._selected = []; this._shift = []; this.ctrl = null; } Selector.prototype = { select: function (node, ctrlKey, shiftKey) { if (node) { if (node.isSelected) { // node 已选中的情况 if (ctrlKey) { // ctrl+选中node->取消选中 this._unselect(node); } else if (shiftKey) { // shift+选中node->范围选中 this._selectRange(this.ctrl, node); } else { // 选中node->仅选中 this._clear(node); } } else { // node 未选中的情况 if (ctrlKey) { // ctrl+未选node->增添选中 this._select(node); } else { if (shiftKey) { // shift+未选node if (this.ctrl) { // +已有ctrl->范围选中 this._selectRange(this.ctrl, node); } else { // 没有ctrl->仅选中 this._select(node); } } else { // 选中node->仅选中 this._clear(node); } } } } else if (!ctrlKey && !shiftKey) { this._clear(); // 点击屏幕->清除选中 } }, isMultiple: function () { return this.selected.length > 1; }, sideBySide: function () { for (var i = this.selected.length; i > 0;) { if (this.selected[--i].parent !== this.ctrl.parent) return false; } return true; }, forEachSelected: function (callback, context) { for (var i = 0, length = this.selected.length; i < length; i++) this._selected[i] = this.selected[i]; for (i = 0; i < length; i++) callback.call(context, this._selected[i]); }, onCtrlSelected: janvas.Utils.noop, onNodeSelected: janvas.Utils.noop, onClear: janvas.Utils.noop, _select: function (node) { node.select(); this.selected.push(node); if (this.ctrl === null) this.onCtrlSelected(this.ctrl = node); else this.onNodeSelected(node); }, _unselect: function (node) { node.unselect(); this.selected.splice(this.selected.indexOf(node), 1); }, _clear: function (except) { while (this.selected.length) this.selected.pop().unselect(); this._shift.length = 0; this.ctrl = null; if (except) this._select(except); else this.onClear(); }, _selectRange: function (ctrl, node) { if (node.depth === ctrl.depth && node !== ctrl) { while (this._shift.length) { var pop = this._shift.pop(); if (pop.isSelected) this._unselect(pop); } ctrl.parent.forEachChild(function (child) { if (!child.isSelected) this._select(child); this._shift.push(child); }, this, node.index, ctrl.index); if (!ctrl.isSelected) this._select(ctrl); } }, get last() { return this.selected[this.selected.length - 1]; }, get length() { return this.selected.length; } }; return Selector; }()), Hacker: (function () { Hacker.unit = "px"; function Hacker() { this._init(); this._initStyles(); this._initCallbacks(); } Hacker.prototype = { _init: function () { this.border = document.createElement("div"); this.border.appendChild(this.textarea = document.createElement("textarea")); this.deactivate(); }, _initStyles: function () { var border = this.border.style, textarea = this.textarea.style; border.position = "absolute"; // border.display = "block"; border.borderStyle = "solid"; border.margin = "0"; textarea.display = "block"; textarea.overflow = "hidden"; textarea.outline = textarea.border = textarea.resize = "none"; textarea.wordWrap = textarea.whiteSpace = "break-spaces"; textarea.wordBreak = "break-all"; textarea.boxSizing = "border-box"; textarea.margin = "0"; textarea.tabSize = "1"; }, _initCallbacks: function () { var border = this.border; border.onmousedown = border.onmousemove = border.onmouseup = border.ontouchstart = border.ontouchmove = border.ontouchend = border.onmouseout = border.ontouchcancel = border.ondblclick = function (ev) { ev.stopPropagation(); }; border.oncontextmenu = function (ev) { ev.stopPropagation(); ev.preventDefault(); // TODO: 使用 oncontextmenu(x, y) 回调出去,调用 Menu 菜单 }; border.onkeydown = function (ev) { var key = ev.key; if (this.isActivated()) { ev.stopPropagation(); switch (key) { case "s": case "o": if (ev.ctrlKey) ev.preventDefault(); break; case "Tab": ev.preventDefault(); break; case "Escape": this._bind.setValue(this._value); this.deactivate(); break; case "Enter": if (ev.ctrlKey) { var value = this.getValue(), start = this.selectionStart, end = this.selectionEnd; this.setValue(value.substr(0, start) + "\n" + value.substr(end)); this.selectionEnd = start + 1; this._oninput(); } else if (!ev.shiftKey) { ev.preventDefault(); this.deactivate(); } break; } } else if (!ev.ctrlKey && key.length === 1 || ev.isComposing || key === "Process") { if (key === " ") ev.preventDefault(); ev.stopPropagation(); this.activate(); } }.bind(this); border.oninput = this._oninput.bind(this); }, appendTo: function (wrapper) { wrapper.appendChild(this.border); }, bind: function (node) { if (this.isActivated()) this.deactivate(); this._bind = node; this.textarea.focus(); }, activate: function () { this.beforeActivate(this._bind); this._isActivated = true; this.border.style.opacity = "1"; this.border.style.pointerEvents = "auto"; this._bindParams(); this._bindStyles(); this._bind.hide(); this.callDraw(); }, deactivate: function () { this._isActivated = false; this.border.style.opacity = "0"; this.border.style.pointerEvents = "none"; if (this._bind) { this._bind.show(); if (this._bind.getValue() === this._value) this.callDraw(); else this._bind.onValueOperation(this._value); } }, isActivated: function () { return this._isActivated; }, _bindParams: function () { this.follow(); this.setWidth(this._bind.width); this.setHeight(this._bind.height); this.setValue(this._value = this._bind.getValue()); this.textarea.select(); }, _bindStyles: function () { var border = this.border.style, textarea = this.textarea.style, bind = this._bind.style; border.borderRadius = bind.borderRadius + bind._borderOffset + Hacker.unit; border.padding = bind.borderPadding + Hacker.unit; border.borderWidth = bind.borderWidth + Hacker.unit; border.borderColor = bind.borderColor; textarea.backgroundColor = bind.backgroundColor; textarea.color = bind.color; textarea.font = bind.font; // textarea.fontFamily = bind.fontFamily; textarea.fontSize = bind.fontSize + Hacker.unit; textarea.lineHeight = bind.lineHeight + Hacker.unit; textarea.pad.........完整代码请登录后点击上方下载按钮下载查看
网友评论0