jsmind
Version:
jsMind is a pure javascript library for mindmap, it base on html5 canvas. jsMind was released under BSD license, you can embed it in any project, if only you observe the license.
352 lines (315 loc) • 11.8 kB
JavaScript
/**
* @license BSD
* @copyright 2014-2025 hizzgdev@163.com
*
* Project Home:
* https://github.com/hizzgdev/jsmind/
*/
(function ($w) {
'use strict';
console.warn("The version is outdated. see details: https://hizzgdev.github.io/jsmind/es6/")
var __name__ = 'jsMind';
var jsMind = $w[__name__];
if (!jsMind) { return; }
if (typeof jsMind.screenshot != 'undefined') { return; }
var $d = $w.document;
var $c = function (tag) { return $d.createElement(tag); };
var css = function (cstyle, property_name) {
return cstyle.getPropertyValue(property_name);
};
var is_visible = function (cstyle) {
var visibility = css(cstyle, 'visibility');
var display = css(cstyle, 'display');
return (visibility !== 'hidden' && display !== 'none');
};
var jcanvas = {};
jcanvas.rect = function (ctx, x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
};
jcanvas.text_multiline = function (ctx, text, x, y, w, h, lineheight) {
var line = '';
var text_len = text.length;
var chars = text.split('');
var test_line = null;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
for (var i = 0; i < text_len; i++) {
test_line = line + chars[i];
if (ctx.measureText(test_line).width > w && i > 0) {
ctx.fillText(line, x, y);
line = chars[i];
y += lineheight;
} else {
line = test_line;
}
}
ctx.fillText(line, x, y);
};
jcanvas.text_ellipsis = function (ctx, text, x, y, w, h) {
var center_y = y + h / 2;
var text = jcanvas.fittingString(ctx, text, w);
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(text, x, center_y, w);
};
jcanvas.fittingString = function (ctx, text, max_width) {
var width = ctx.measureText(text).width;
var ellipsis = '…';
var ellipsis_width = ctx.measureText(ellipsis).width;
if (width <= max_width || width <= ellipsis_width) {
return text;
} else {
var len = text.length;
while (width >= max_width - ellipsis_width && len-- > 0) {
text = text.substring(0, len);
width = ctx.measureText(text).width;
}
return text + ellipsis;
}
};
jcanvas.image = function (ctx, url, x, y, w, h, r, rotation, callback) {
var img = new Image();
img.onload = function () {
ctx.save();
ctx.translate(x, y);
ctx.save();
ctx.beginPath();
jcanvas.rect(ctx, 0, 0, w, h, r);
ctx.closePath();
ctx.clip();
ctx.translate(w / 2, h / 2);
ctx.rotate(rotation * Math.PI / 180);
ctx.drawImage(img, -w / 2, -h / 2);
ctx.restore();
ctx.restore();
!!callback && callback();
}
img.src = url;
};
jsMind.screenshot = function (jm) {
this.jm = jm;
this.canvas_elem = null;
this.canvas_ctx = null;
this._inited = false;
};
jsMind.screenshot.prototype = {
init: function () {
if (this._inited) { return; }
console.log('init');
var c = $c('canvas');
var ctx = c.getContext('2d');
this.canvas_elem = c;
this.canvas_ctx = ctx;
this.jm.view.e_panel.appendChild(c);
this._inited = true;
this.resize();
},
shoot: function (callback) {
this.init();
this._draw(function () {
!!callback && callback();
this.clean();
}.bind(this));
this._watermark();
},
shootDownload: function () {
this.shoot(function () {
this._download();
}.bind(this));
},
shootAsDataURL: function (callback) {
this.shoot(function () {
!!callback && callback(this.canvas_elem.toDataURL());
}.bind(this));
},
resize: function () {
if (this._inited) {
this.canvas_elem.width = this.jm.view.size.w;
this.canvas_elem.height = this.jm.view.size.h;
}
},
clean: function () {
var c = this.canvas_elem;
this.canvas_ctx.clearRect(0, 0, c.width, c.height);
},
_draw: function (callback) {
var ctx = this.canvas_ctx;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
this._draw_lines(function () {
this._draw_nodes(callback);
}.bind(this));
},
_watermark: function () {
var c = this.canvas_elem;
var ctx = this.canvas_ctx;
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#000';
ctx.font = '11px Verdana,Arial,Helvetica,sans-serif';
ctx.fillText('github.com/hizzgdev/jsmind', c.width - 5.5, c.height - 2.5);
ctx.textAlign = 'left';
ctx.fillText($w.location, 5.5, c.height - 2.5);
},
_draw_lines: function (callback) {
this.jm.view.graph.copy_to(this.canvas_ctx, callback);
},
_draw_nodes: function (callback) {
var nodes = this.jm.mind.nodes;
var node;
for (var nodeid in nodes) {
node = nodes[nodeid];
this._draw_node(node);
}
function check_nodes_ready() {
console.log('check_node_ready' + new Date());
var allOk = true;
for (var nodeid in nodes) {
node = nodes[nodeid];
allOk = allOk & node.ready;
}
if (!allOk) {
$w.setTimeout(check_nodes_ready, 200);
} else {
$w.setTimeout(callback, 200);
}
}
check_nodes_ready();
},
_draw_node: function (node) {
var ctx = this.canvas_ctx;
var view_data = node._data.view;
var node_element = view_data.element;
var ncs = getComputedStyle(node_element);
if (!is_visible(ncs)) {
node.ready = true;
return;
}
var bgcolor = css(ncs, 'background-color');
var round_radius = parseInt(css(ncs, 'border-top-left-radius'));
var color = css(ncs, 'color');
var padding_left = parseInt(css(ncs, 'padding-left'));
var padding_right = parseInt(css(ncs, 'padding-right'));
var padding_top = parseInt(css(ncs, 'padding-top'));
var padding_bottom = parseInt(css(ncs, 'padding-bottom'));
var text_overflow = css(ncs, 'text-overflow');
var font = css(ncs, 'font-style') + ' ' +
css(ncs, 'font-variant') + ' ' +
css(ncs, 'font-weight') + ' ' +
css(ncs, 'font-size') + '/' + css(ncs, 'line-height') + ' ' +
css(ncs, 'font-family');
var rb = {
x: view_data.abs_x,
y: view_data.abs_y,
w: view_data.width + 1,
h: view_data.height + 1
};
var tb = {
x: rb.x + padding_left,
y: rb.y + padding_top,
w: rb.w - padding_left - padding_right,
h: rb.h - padding_top - padding_bottom
};
ctx.font = font;
ctx.fillStyle = bgcolor;
ctx.beginPath();
jcanvas.rect(ctx, rb.x, rb.y, rb.w, rb.h, round_radius);
ctx.closePath();
ctx.fill();
ctx.fillStyle = color;
if ('background-image' in node.data) {
var backgroundUrl = css(ncs, 'background-image').slice(5, -2);
node.ready = false;
var rotation = 0;
if ('background-rotation' in node.data) {
rotation = node.data['background-rotation'];
}
jcanvas.image(ctx, backgroundUrl, rb.x, rb.y, rb.w, rb.h, round_radius, rotation,
function () {
node.ready = true;
});
}
if (!!node.topic) {
if (text_overflow === 'ellipsis') {
jcanvas.text_ellipsis(ctx, node.topic, tb.x, tb.y, tb.w, tb.h);
} else {
var line_height = parseInt(css(ncs, 'line-height'));
jcanvas.text_multiline(ctx, node.topic, tb.x, tb.y, tb.w, tb.h, line_height);
}
}
if (!!view_data.expander) {
this._draw_expander(view_data.expander);
}
if (!('background-image' in node.data)) {
node.ready = true;
}
},
_draw_expander: function (expander) {
var ctx = this.canvas_ctx;
var ncs = getComputedStyle(expander);
if (!is_visible(ncs)) { return; }
var style_left = css(ncs, 'left');
var style_top = css(ncs, 'top');
var font = css(ncs, 'font');
var left = parseInt(style_left);
var top = parseInt(style_top);
var is_plus = expander.innerHTML === '+';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(left + 7, top + 7, 5, 0, Math.PI * 2, true);
ctx.moveTo(left + 10, top + 7);
ctx.lineTo(left + 4, top + 7);
if (is_plus) {
ctx.moveTo(left + 7, top + 4);
ctx.lineTo(left + 7, top + 10);
}
ctx.closePath();
ctx.stroke();
},
_download: function () {
var c = this.canvas_elem;
var name = this.jm.mind.name + '.png';
if (navigator.msSaveBlob && (!!c.msToBlob)) {
var blob = c.msToBlob();
navigator.msSaveBlob(blob, name);
} else {
var bloburl = this.canvas_elem.toDataURL();
var anchor = $c('a');
if ('download' in anchor) {
anchor.style.visibility = 'hidden';
anchor.href = bloburl;
anchor.download = name;
$d.body.appendChild(anchor);
var evt = $d.createEvent('MouseEvents');
evt.initEvent('click', true, true);
anchor.dispatchEvent(evt);
$d.body.removeChild(anchor);
} else {
location.href = bloburl;
}
}
},
jm_event_handle: function (type, data) {
if (type === jsMind.event_type.resize) {
this.resize();
}
}
};
var screenshot_plugin = new jsMind.plugin('screenshot', function (jm) {
var jss = new jsMind.screenshot(jm);
jm.screenshot = jss;
jm.shoot = function () {
jss.shoot();
};
jm.add_event_listener(function (type, data) {
jss.jm_event_handle.call(jss, type, data);
});
});
jsMind.register_plugin(screenshot_plugin);
})(window);