JavaScript 绘制流程图

2016/10/02 | 4分钟阅读 | 更新于 2016/10/02

从1号写到2号了- - 上午本来是在写一个小项目的文档,大致是这么个样子

1. xxx xxx xxx
2. xxx xxx xxx
3. xxx,xxx条件到 3.1,xxx条件到3.2
3.1 xxx
3.2 xxx 结束
4. xxx

一想,这算是流程图?感觉如果能顺手转换成流程图会很不错 但是写的时候 3.1,3.2 是 3 的分支,其实跟 3 作为主线的下一个节点 4 其实是一层,于是我把 3.1,3.2改成了 4.1,4.2 然后这样就很微妙了 1,2,3 这样表示主分支,带小数表示分支。然后这样每个节点正好对应网格中一个位置。写文字的时候如果有分支需要手动指定,不指定的话默认进入下一层主分支。之后只要确定从某点到某点的画法就好了。

对于每个图形,有固定的连接点用来连线

/*
列.行 -> 坐标
'm.n' -> row = m, col = n
'm' -> row = m, col = 0
*/
var pos = function () {
  var args = Array.prototype.slice.call(arguments),
    pos = [],
    mw = config.minWidth,
    mh = config.minHeight,
    rs = config.rowSpacing,
    cs = config.colSpacing,
    pt = config.top,
    pl = config.left,
    pr = config.right,
    pb = config.bottom,
    args = args.join(".").split(".");
  ((r = +args[0]),
    (c = +args[1] || +0),
    (x = pl + c * (mw + cs)),
    (y = pt + r * (mh + rs)),
    (pos = [r, c]));
  return {
    x: x,
    y: y,
    row: r,
    col: c,
    id: pos,
    cen: [x + mw / 2, y + mh / 2],
    top: [x + mw / 2, y],
    bottom: [x + mw / 2, y + mh],
    left: [x, y + mh / 2],
    right: [x + mw, y + mh / 2],
    up: [x + mw / 2, y - rs / 2],
    down: [x + mw / 2, y + rs / 2],
  };
};

这个函数会传入一个或两个参数,格式可能是数字或者字符串,字符串可能带小数部分也可能不带,所以拿 参数靠解析 arguments 获得。然后之前做笔试题的时候发现这东西输出之后居然是个对象,但是在 chrome 里输出“长得像”数组,后面的 document.querySelectorAll 也类似,搜了一下找到处理办法:

关于 arguments arguments 对象并不是一个真正的Array。它类似于数组,但没有数组所特有的属性和方法,除了 length。例如,它没有 pop 方法。不过可以将其转换成数组:

var args = Array.prototype.slice.call(arguments);

From: arguments - JavaScript | MDN

现在可以获得(行,列)对应的图形的各个连接点的坐标了。然后只要在两点之间画线就好了,写个函数分别处理各种情况,由上到下怎么办,左到右,右到左,左上右下等等 …

var line = function (p0, p1) {
  ctx.beginPath();
  if (p0.x < p1.x && p0.y == p1.y) {
    /* 左到右 */
    arrow(p1.left, 1);
    ctx.moveTo(...p0.right);
    ctx.lineTo(...p1.left);
  } else if (p0.x > p1.x && p0.y == p1.y) {
    /* 左到右 */
    arrow(p1.right, 3);
    ctx.moveTo(...p0.left);
    ctx.lineTo(...p1.right);
  } else if (p0.x == p1.x && p0.y < p1.y) {
    /* 从上到下 */
    arrow(p1.top, 2);
    ctx.moveTo(...p0.bottom);
    ctx.lineTo(...p1.top);
  } else if (p0.x == p1.x && p0.y > p1.y) {
    /* 从下到上 */
    var pt1 = pos(p0.row, p0.col + 1),
      pt2 = pos(p1.row, p0.col + 1);
    arrow(p1.bottom, 0);
    ctx.moveTo(...p0.right);
    ctx.lineTo(...pt1.cen);
    ctx.lineTo(...pt2.cen);
    ctx.lineTo(...p1.right);
  } else if (p0.x > p1.x && p0.y > p1.y) {
    /* 从右下到左上 */
    var pt = pos(p1.row, p0.col);
    arrow(p1.right, 3);
    ctx.moveTo(...p0.top);
    ctx.lineTo(...pt.cen);
    ctx.lineTo(...p1.right);
  } else if (p0.x < p1.x && p0.y < p1.y) {
    /* 左上 - 右下 */
    var pt = pos(p0.row, p1.col);
    arrow(p1.top, 2);
    ctx.moveTo(...p0.right);
    ctx.lineTo(...pt.cen);
    ctx.lineTo(...p1.top);
  } else if (p0.x > p1.x && p0.y < p1.y) {
    /* 右上 - 左下 */
    var pt1 = [p0.cen[0], p0.bottom[1] + config.rowSpacing / 2],
      pt2 = [p1.cen[0], p0.bottom[1] + config.rowSpacing / 2];
    arrow(pt2, 3);
    ctx.moveTo(...p0.bottom);
    ctx.lineTo(...pt1);
    ctx.lineTo(...pt2);
    ctx.lineTo(...p1.top);
  }
  ctx.stroke();
};

展开运算符 很好用。

功能实现之后就该配置了,写了一堆 input。要挨个获取值么?能不能搞个数据绑定,找了下相关的文章。

数据劫持: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 From: 剖析Vue原理&实现双向绑定MVVM

我没有往下细看,直接去了 MDN 找相关文档

get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。 set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。 From: Object.defineProperty() - JavaScript | MDN

Chrome 有 setAttribute 和 getAttribute,然后加上这篇文章,CSS属性选择器驱动的过滤搜索技术 « 张鑫旭-鑫空间-鑫生活 ,于是我放心的给 input 加了 fc-bind="config.left" 这样的属性。现在我可以用 document.querySelectorAll('[fc-bind]') 选择所有需要绑定的元素了。 嗯?document.querySelectorAll 好像太长了

var els = document.querySelectorAll;
/* 报错!? */
var els = function (str) {
  document.querySelectorAll(str);
};
/* 报错!? */

Google 一下:

querySelector is not a generic method, it will not accept another this value. So, if you want a shortcut, you must make sure your querySelector is bound to the document:

var qs = document.querySelector.bind( document ); From: javascript - Illegal invocation with document.querySelector - Stack Overflow

好吧 …

const els = document.querySelectorAll.bind(document);
var bs = Array.prototype.slice.call(els("[fc-bind]"));
var fc = {};
var fcBind = function (obj, prop, el) {
  Object.defineProperty(obj, prop.toString(), {
    get: function () {
      var val = el.value;
      if (/^#/.test(val)) {
        return val;
      } else if (/^\d*$/.test(val)) {
        return parseInt(val);
      } else {
        return val;
      }
    },
    set: function (val) {
      el.value = val;
    },
  });
};
bs.map(function (one, id) {
  var toDo = one.getAttribute("fc-bind");
  var items = toDo.split(".");
  t = fc;
  items.map((item, id) => {
    t[item] = t[item] || {};
    t = t[item];
    if (id == items.length - 2) {
      fcBind(t, items[id + 1].toString(), one);
    }
  });
});

差不多就是这样

另外关于文字列表,参考中文字体网页开发指南 - 阮一峰的网络日志

© 2026 香蕉引擎故障报告

🌱 Powered by Hugo with theme Dream.

关于

要怎么介绍自己呢,🤔。

很早以前是作为 Web 前端在学习的,但是工作第一年就成为了全干工程师。喜欢尝试各种东西,什么都会一点。

一直很喜欢 Ebiten 游戏引擎 ,特别简洁,用它做过一些小东西,可以查看这个分类 。另外特别推荐这个木鱼 ,是一个相对完整的小玩意儿,包含手搓的一个简单的 UI 框架;支持鼠标和键盘操作;有多语言和主题切换功能;同时支持 Web 端和客户端。它的源代码在 bin16/wooden-fish

主题

网站基于 Hugo,当前使用的是 hugo-theme-dream 主题的修改版 ,根据我的需要,做了一些对 PaperMod 的兼容。

我自己也写过主题 ,但是没有别人写的好看。

正在从我的笔记中往外搬运内容

等待更新:

  • 从《锈湖》中学了些什么东西
  • 我拿 React 写解谜游戏的经过
  • 基于 Pocketbase 的 Pocket Memos
  • 数独!