js+css改造jsmind实现思维导图 | 树状图[通俗易懂]

js+css改造jsmind实现思维导图 | 树状图[通俗易懂]一.调研树状图|思维导图调研1.https://juejin.cn/post/68449041032989900932.echartshttps://echarts.apache.org/examples/zh/editor.html?c=tree-polyline3.https://juejin.cn/post/68449036154259374164.https://bl.ocks.org/mbostock/43390835.https://fperucic.github.io/tr

大家好,欢迎来到IT知识分享网。

一. 调研

1.常见树状图 | 思维导图

  1. https://juejin.cn/post/6844904103298990093

  2. echarts
    https://echarts.apache.org/examples/zh/editor.html?c=tree-polyline

  3. https://juejin.cn/post/6844903615425937416

  4. https://bl.ocks.org/mbostock/4339083

  5. https://fperucic.github.io/treant-js/

  6. gojs
    https://gojs.net/latest/samples/decisionTree.html
    https://balkan.app/OrgChartJS/Demos/RoyalFamilyTree
    https://codepen.io/tutsplus/pen/MWedpoj

  7. 有复选框的treeNode
    https://devexpress.github.io/dotnet-eud/interface-elements-for-web/articles/tree-view/tree-view-nodes-checking.html


2.对比结果

拟采用的方法:

改造 jsmind 实现自定义的思维导图

Jsmind 的功能及使用见官方文档:

https://github.com/hizzgdev/jsmind/blob/master/docs/zh/index.md


二. 文件目录

在这里插入图片描述

1. public\jsmind.css

jsmind 插件自带的 css 文件,思维导图的样式文件。

2. public\jsmind.js

jsmind 插件自带的 js 文件,思维导图的逻辑实现。

3. public\mind.js

思维导图的数据和触发方法,数据挂载在 window 对象下,用于和 vue 框架中的内容交互。

4. src\components\minder\DataMind.vue

思维导图的业务调用组件,包括思维导图、标题栏和左上 tip 文字内容。

5. src\components\minder\Minder.vue

思维导图的核心组件,分为 5 种类型:defalut,check,label,label2,input,通过 type 传值配置对应样式。
!!!当然这只是我根据项目需求改造的几种样式,大家也可以自行阅读下面minder.vuejsmind.css以及jsmind.js进行个性化改造。


三. 功能点实现

1.介绍

Jsmind的功能及使用见官方文档:

https://github.com/hizzgdev/jsmind/blob/master/docs/zh/index.md
本demo采用 vue3+typescript+vite 实现,核心功能在 src\components\minder\Minder.vue 文件中实现。

2.在 index.html 中引入 jsmind

如下图所示:

<link rel="stylesheet" href="/jsmind.css" />
<script type="text/javascript" src="/jsmind.js"></script>

3.页面中引入 Minder 组件

如下所示:

在这里插入图片描述

4.传入的数据结构,如下所示:

   (window as any).mindData = { 
   
        id: "root",
        topic: "根节点名字",
        number: [88],
        children: [
          { 
   
            id: "easy",
            topic: "规章制度",
            children: [
              { 
    id: "easy1", topic: "请休假制度" },
              { 
    id: "easy2", topic: "考勤制度" },
            ],
          },
          { 
   
            id: "open",
            topic: "岗位职责",
            children: [
              { 
   
                id: "open1",
                topic: "人事部职责",
                children: [
                  { 
    id: "open1-1", topic: "行政管理" },
                  { 
    id: "open1-2", topic: "人事管理" },
                ],
              },
              { 
   
                id: "open2",
                topic: "信息部职责",
                children: [
                  { 
    id: "open2-1", topic: "信息安全" },
                  { 
    id: "open2-2", topic: "信息排查" },
                  { 
    id: "open2-3", topic: "信息汇总" },
                  { 
    id: "open2-4", topic: "ERP" },
                ],
              },
              { 
   
                id: "open3",
                topic: "生产车间",
                children: [
                  { 
    id: "open3-1", topic: "安全制度" },
                  { 
    id: "open3-2", topic: "车间操作手册" },
                ],
              },
            ],
          },
          { 
   
            id: "powerful",
            topic: "员工福利",
            number,
            children: [
              { 
    id: "powerful1", topic: "雪天", tip: "3366" },
              { 
    id: "powerful2", topic: "年假", tip: "98999" },
              { 
   
                id: "powerful3",
                topic: "法定节假日",
                number,
              },
              { 
    id: "powerful4", topic: "生日祝福" },
              { 
   
                id: "powerful5",
                topic: "成长与进步",
                children: [{ 
    id: "powerful5-1", topic: "员工培训" }],
              },
            ],
          },
        ],
      };

其中,id,topic,children 的用法见官方文档,number 为定制化展示的数据部分,如下图所示:
在这里插入图片描述

当 type 为 label2 时,number 为数组,对应关系如下图所示:
在这里插入图片描述

  1. type 分为 5 种类型:default,check,label,label2,input,当 type 为 default 类型时,传值 type=“”即可,页面效果如下:
    在这里插入图片描述

当 type 为 check 类型时,页面效果如下:
在这里插入图片描述

当 type 为 label 类型时,页面效果如下:

在这里插入图片描述

当 type 为 label2 类型时,页面效果如下:
在这里插入图片描述

当 type 为 input 类型时,页面效果如下:
在这里插入图片描述

  1. 参数配置见下图,可根据实际需求进行调整:
    在这里插入图片描述

四、源码

1. Minder.vue

<template>
  <div class="minder" :id="jsmind_container_id"></div>
</template>

<script lang="ts"> import { 
      defineComponent, reactive, toRefs, onMounted, computed, watch, } from "vue"; declare global { 
      interface Window { 
      metaData: any; uncheckedData: any; } } export default defineComponent({ 
      name: "minder", props: { 
      data: { 
      type: Object, default: { 
     }, }, type: { 
      //type:defalut,check,label,label2,input type: String, default: "", }, jsmind_container_id: { 
      type: String, default: "jsmind_container", }, rootValue: { 
      type: Number, default: 0, }, jsmindType: { 
      type: Number, default: 0, }, }, setup(props) { 
      console.log("data,type,id", props); const state = reactive({ 
      name: "", }); let jm: any; const jwidth = { 
      "": 140, check: 140, label: 158, label2: 216, input: 178, }; const mind = computed(() => ({ 
      meta: { 
      name: "jsMind", author: "", version: "0.2", }, format: "node_tree", data: props.data, })); const handleMind = (data: any) => { 
      console.log("props", props); const { 
      id, topic, children, number = [50], topic_en, tip } = data; console.log("data", data); //如果是div,不是字符串,不予处理 if (topic.includes("<div")) return; const isParent: boolean = children && children.length > 0; const isRoot: boolean = id === "root"; const jdom = getDomByType( props.type, topic, isParent, id, topic_en, number ); const jtip = getTipByType(props.type, tip, isParent, number); if (isRoot) { 
      //根节点单独处理 data.topic = getRootByType(props.type, topic, props.rootValue); } // else if (props.type === "label2" && isParent) { 
      // //label2类型父节点底部有文字 // data.topic = ` // <div class="jmnnode-base"> // ${jdom} // ${jtip} // </div>`; // } else if (props.type != "label2" && !isParent) { 
      //其他类型叶子节点底部有文字 data.topic = ` <div class="jmnnode-base"> ${ 
       jdom} ${ 
       jtip} </div>`; } //20211225---label2只有叶子结点显示均值方差 else if (props.type == "label2" && !isParent) { 
      //其他类型叶子节点底部有文字 data.topic = ` <div class="jmnnode-base"> ${ 
       jdom} ${ 
       jtip} </div>`; } else { 
      data.topic = jdom; } if (isParent) { 
      for (let child of children) { 
      handleMind(child); } } }; const getDomByType = ( type: string, topic: string, isParent: boolean, id: string, topic_en: string, number?: number | number[] ) => { 
      // console.log("type##########", type); let resDom = ""; let jclass = isParent ? `class="jmnnode-dom jmnnode-dom-blue"` : `class="jmnnode-dom"`; switch (type) { 
      case "check": if (isParent) { 
      resDom = `<svg-icon icon-class="icon_unbiased"></svg-icon> <div ${ 
       jclass} style="width:${ 
       jwidth[type]}px">${ 
       topic}</div>`; } else { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth[type]}px"> <span>${ 
       topic}</span> </div> <input type="checkbox" οnclick="handleClick(this.checked,'${ 
       topic}','${ 
       id}','${ 
       topic_en}')"/>`; } break; case "label": jclass = isParent ? `class="jmnnode-dom jmnnode-dom-blue jmnnode-dom-label"` : `class="jmnnode-dom jmnnode-dom-label"`; resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth[type]}px"> <span>${ 
       topic}</span> <div class="jmnnode-dom-number">${ 
       number || 0}</div> </div>`; break; case "label2": jclass = isParent ? `class="jmnnode-dom jmnnode-dom-blue jmnnode-dom-label"` : `class="jmnnode-dom jmnnode-dom-label"`; const [num1, num2, num3, num4] = (number as number[]) || [0, 0, 0, 0]; //20211226---修改只有叶子结点显示权重方差 if (isParent) { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth[type] - 30}px"> <span>${ 
       topic}</span> <div class="jmnnode-dom-avg"> <div class="jmnnode-dom-number">${ 
       num1?.toFixed(2)}</div> </div> </div>`; } else { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth[type] - 30}px"> <span>${ 
       topic}</span> <div class="jmnnode-dom-avg"> <div class="jmnnode-dom-number">${ 
       num1?.toFixed(2)}</div> <div class="jmnnode-dom-number jmnnode-dom-green">${ 
       num2?.toFixed( 2 )}</div> </div> </div>`; } break; case "input": jclass = isParent ? `class="jmnnode-dom jmnnode-dom-blue jmnnode-dom-input"` : `class="jmnnode-dom jmnnode-dom-input"`; resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth[type]}px"> <span>${ 
       topic}</span> <input class="jmnnode-dom-input-right" value='${ 
       number}' οnchange="handleInput(this.value,'${ 
       topic}','${ 
       id}')"/> </div>`; break; default: if (topic == "完整性") { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth["check"]}px"> <img src="/src/assets/img/icon_whole.png" width="22" height="22" style=" position: absolute;left: 12px;top: 16px;" /> ${ 
       topic}</div>`; } else if (topic == "现实性") { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth["check"]}px"> <img src="/src/assets/img/icon_clock.png" width="22" height="22" style=" position: absolute;left: 12px;top: 16px;" /> ${ 
       topic}</div>`; } else if (topic == "准确性") { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth["check"]}px"> <img src="/src/assets/img/icon_right.png" width="22" height="22" style=" position: absolute;left: 12px;top: 16px;" /> ${ 
       topic}</div>`; } else if (topic == "无偏性") { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth["check"]}px"> <img src="/src/assets/img/icon_unbiased.png" width="22" height="22" style=" position: absolute;left: 12px;top: 16px;" /> ${ 
       topic}</div>`; } else if (topic == "规范性") { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth["check"]}px"> <img src="/src/assets/img/icon_standard.png" width="22" height="22" style=" position: absolute;left: 12px;top: 16px;" /> ${ 
       topic}</div>`; } else { 
      resDom = ` <div ${ 
       jclass} style="width:${ 
       jwidth["check"]}px"> ${ 
       topic}</div>`; } } return resDom; }; const getTipByType = ( type: string, tip: string = "0", isParent: boolean, number: number[] = [0, 0, 0, 0] ) => { 
      let resDom = ""; switch (type) { 
      case "label2": const [, , num3, num4] = (number as number[]) || [0, 0, 0, 0]; resDom = ` <div class="jmnnode-tip jmnnode-tip-avg" style="width:${ 
        jwidth[type] }px"> 方差:<div class="jmnnode-tip-blue" style="margin-right:32px">${ 
       num3?.toFixed( 2 )}</div> 均值:<div class="jmnnode-tip-yellow">${ 
       num4?.toFixed(2)}</div> </div>`; break; default: resDom = `<div class="jmnnode-tip" style="width:${ 
       jwidth["check"]}px">(${ 
       tip})</div>`; } return resDom; }; const getRootByType = (type: string, topic: string, rootValue: number) => { 
      let resDom = ""; switch (type) { 
      case "label2": resDom = ` <div class="jmnnode-base"> <div class="jmnnode-dom">${ 
       topic}</div> <div class="jmnnode-root-line"></div> <div class="jmnnode-root-tip"> <div class="jmnnode-root-tip-title">综合评估:</div> <div class="jmnnode-root-tip-blue">${ 
       rootValue || 0}</div> </div> </div>`; break; default: resDom = ` <div class="jmnnode-base"> <div class="jmnnode-dom" style="display:block" > <img src="/src/assets/img/pic_demo.png" width="33" height="33" />${ 
       topic}</div> <div class="jmnnode-root-line"></div> </div>`; } return resDom; }; const options = { 
      container: props.jsmind_container_id as String, //容器的ID editable: false, // 是否启用编辑 theme: "primary", //主题 mode: "side", // 显示模式========full - 子节点动态分布在根节点两侧 [默认值] side - 子节点只分布在根节点右侧 layout: { 
      hspace: 50, // 节点之间的水平间距 vspace: 40, // 节点之间的垂直间距 pspace: 13, // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器) }, //options的属性 //container : '', // [必选] 容器的ID // editable : false, // 是否启用编辑 // theme : null, // 主题 // mode :'full', // 显示模式========full - 子节点动态分布在根节点两侧 [默认值] side - 子节点只分布在根节点右侧 // support_html : true, // 是否支持节点里的HTML元素 view: { 
      hmargin: 0, // 思维导图距容器外框的最小水平距离 vmargin: 100, // 思维导图距容器外框的最小垂直距离 line_width: 1, // 思维导图线条的粗细 line_color: "#C6C6C9", // 思维导图线条的颜色 }, // layout:{ 
      // hspace:30, // 节点之间的水平间距 // vspace:20, // 节点之间的垂直间距 // pspace:13 // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器) // }, }; if (props.jsmindType == 1) { 
      options.layout = { 
      hspace: 180, // 节点之间的水平间距 vspace: 40, // 节点之间的垂直间距 pspace: 13, // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器) }; } //handleMind(mind.value.data); const showMinder = () => { 
      console.log(props); console.log("handleMind", JSON.parse(JSON.stringify(mind.value.data))); window.metaData = JSON.parse(JSON.stringify(mind.value.data)); window.uncheckedData = JSON.parse(JSON.stringify(mind.value.data)); handleMind(mind.value.data); // console.log("handleMind2", mind.value.data); jm.show(mind.value); //jm.disable_edit();//禁止编制 jm.expand_all(); //展开全部节点 // jm.add_node(parent_node, nodeid, topic, data);//添加节点 // setHeight(); }; // const setHeight = () => { 
      // setTimeout(() => { 
      // const dom = document.querySelector("jmnodes") as HTMLDivElement; // // const btn_dom = document.querySelector( // // ".next-step-btn" // // ) as HTMLDivElement; // ( // document.querySelector( // `#${props.jsmind_container_id}` // ) as HTMLDivElement // ).style.height = `${dom.clientHeight}px`; // }, 0); // }; watch( () => props.data, (newVal, oldVal) => { 
      if (newVal.id) showMinder(); } ); onMounted(() => { 
      // @ts-ignore // if (jsmindType == 1) { 
      jm = new jsMind(options); // } else { 
      // jm = new jsMind(options); // } jm.add_event_listener(function (type, data) { 
      console.log("this", jm.view.size.h); const dom = document.querySelector("jmnodes") as HTMLDivElement; console.log("dom.clientHeight", dom.clientHeight); ( document.querySelector( `#${ 
       props.jsmind_container_id}` ) as HTMLDivElement ).style.minHeight = `${ 
       dom.clientHeight}px`; }); if (props.data.id) showMinder(); }); return { 
      ...toRefs(state), }; }, }); </script>
<style lang="scss" scoped> .minder { 
      width: 100%; height: 100%; .jmnode-icon { 
      position: absolute; left: 12px; top: 16px; } } </style>

2. DataMinder.vue

<template>
  <div class="data-mind">
    <!-- <div class="data-mind-title" v-if="type != 'label2'">指标和参数配置</div> -->
    <div class="data-mind-header">
      <div class="data-mind-tip">
        <div class="data-mind-tip__label"></div>
        <div class="data-mind-tip__name">质量特性</div>
      </div>
      <div class="data-mind-tip">
        <div class="data-mind-tip__label2"></div>
        <div class="data-mind-tip__name">度量指标</div>
      </div>
    </div>
    <minder :data="data" :type="type" :jsmind_container_id="jsmind_container_id" :rootValue="rootValue" :jsmindType="jsmindType" ></minder>
  </div>
</template>

<script lang="ts"> import { 
      defineComponent, reactive, toRefs, computed } from "vue"; import Minder from "@/components/minder/Minder.vue"; export default defineComponent({ 
      name: "DataMind", components: { 
      Minder }, props: { 
      data: { 
      type: Object, default: { 
     }, }, type: { 
      //type:defalut,check,label,label2,input type: String, default: "", }, jsmind_container_id: { 
      type: String, default: "jsmind_container", }, rootValue: { 
      type: Number, default: 0, }, jsmindType: { 
      type: Number, default: 0, }, }, setup() { 
      const state = reactive({ 
      name: "", //type:defalut,check,label,label2,input }); return { 
      ...toRefs(state), }; }, }); </script>
<style lang="scss" scoped> $height: 48px; .data-mind { 
      width: 100%; height: 100%; position: relative; &-title { 
      width: 100%; height: $height; background: rgba(84, 115, 231, 0.09); display: flex; align-items: center; padding: 0 20px; font-size: 16px; font-weight: 600; color: #3b3b5b; } &-header { 
      position: absolute; padding: 27px 38px; } &-tip { 
      // position: absolute; // left: 16px; // top: $height + 20; font-size: 14px; color: #9b9ba5; font-weight: 400; display: flex; align-items: center; &__label { 
      width: 28px; height: 12px; background: #f4f7ff; border: 1px solid rgba(223, 223, 223, 1); border-radius: 2px; margin-right: 10px; } &__label2 { 
      width: 28px; height: 12px; background: #ffffff; border: 1px solid rgba(223, 223, 223, 1); border-radius: 2px; margin-right: 10px; } } .circle-leaf { 
      border-radius: 50%; width: 9px; height: 9px; background: rgba(84, 115, 231, 0.7); margin: 0 40px 0 4px; } &-line { 
      line-height: 22px; } .circle-label { 
      background: rgba(84, 115, 231, 0.2); border: 1px solid #5473e7; border-radius: 50%; width: 9px; height: 9px; margin: 0 0 0 8px; } } .square-blue { 
      width: 9px; height: 9px; background: #5473e7; border-radius: 1px; margin-right: 32px; margin-left: 8px; } .square-green { 
      width: 9px; height: 9px; background: #44d7b6; border-radius: 1px; margin-right: 32px; margin-left: 8px; } .square-purple { 
      width: 12px; height: 4px; background: #6236ff; margin-right: 32px; margin-left: 8px; } .square-orange { 
      width: 12px; height: 4px; background: #f7b500; margin-left: 8px; } .square-white { 
      width: 9px; height: 9px; background: #ffffff; border-radius: 1px; border: 1px solid #dfdfdf; } .weight-block { 
      line-height: 22px; display: flex; align-items: center; margin-left: 32px; } .label2-block { 
      line-height: 22px; display: flex; align-items: center; } </style>

3. jsmind.css

/* * Released under BSD License * Copyright (c) 2014-2015 hizzgdev@163.com * * Project Home: * https://github.com/hizzgdev/jsmind/ */

/* important section */
.jsmind-inner { 
   
  position: relative;
  overflow: auto;
  width: 100%;
  height: 100%;
}

/*box-shadow:0 0 2px #000;*/
.jsmind-inner { 
   
  moz-user-select: -moz-none;
  -moz-user-select: none;
  -o-user-select: none;
  -khtml-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

/* z-index:1 */
canvas { 
   
  position: absolute;
  z-index: 1;
}

/* z-index:2 */
jmnodes { 
   
  position: absolute;
  z-index: 2;
  background-color: rgba(0, 0, 0, 0);
}

/*background color is necessary*/
jmnode { 
   
  position: absolute;
  cursor: default;
  max-width: 400px;
  white-space: nowrap;
  /* overflow: hidden; */
  text-overflow: ellipsis;
}

jmexpander { 
   
  position: absolute;
  width: 14px;
  height: 2px;
  margin-top: 6px;
  color: #b9b9bd;
  background: #c6c6c9;
  font-size: 12px;
  text-align: center;
  cursor: pointer;
}

/* default theme */
jmnode { 
   
  /* padding: 10px 20px; */
  background-color: #fff;
  color: #333;
  /* border-radius: 20px; */
  /* box-shadow: 1px 1px 1px #666; */
  font: 16px/1.125 Verdana, Arial, Helvetica, sans-serif;
}

jmnode:hover { 
   
  box-shadow: 2px 2px 8px #000;
  background-color: #ebebeb;
  color: #333;
}

jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
  box-shadow: 2px 2px 8px #000;
}

jmnode.root { 
   
  font-size: 24px;
}

jmexpander:hover { 
   
  border-color: #000;
}

@media screen and (max-device-width: 1024px) { 
   
  jmnode { 
   
    padding: 5px;
    border-radius: 3px;
    font-size: 14px;
  }

  jmnode.root { 
   
    font-size: 21px;
  }
}

/* primary theme */
jmnodes.theme-primary jmnode { 
   
  /* background-color: #428bca; */
  color: #fff;
  /* border-color: #357ebd; */
}

jmnodes.theme-primary jmnode:hover { 
   
  background-color: #3276b1;
  border-color: #285e8e;
}

jmnodes.theme-primary jmnode.selected { 
   
  background-color: #f1c40f;
  color: #fff;
}

jmnodes.theme-primary jmnode.root { 
   
}

jmnodes.theme-primary jmexpander { 
   
}

jmnodes.theme-primary jmexpander:hover { 
   
}

/* warning theme */
jmnodes.theme-warning jmnode { 
   
  background-color: #f0ad4e;
  border-color: #eea236;
  color: #fff;
}

jmnodes.theme-warning jmnode:hover { 
   
  background-color: #ed9c28;
  border-color: #d58512;
}

jmnodes.theme-warning jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-warning jmnode.root { 
   
}

jmnodes.theme-warning jmexpander { 
   
}

jmnodes.theme-warning jmexpander:hover { 
   
}

/* danger theme */
jmnodes.theme-danger jmnode { 
   
  background-color: #d9534f;
  border-color: #d43f3a;
  color: #fff;
}

jmnodes.theme-danger jmnode:hover { 
   
  background-color: #d2322d;
  border-color: #ac2925;
}

jmnodes.theme-danger jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-danger jmnode.root { 
   
}

jmnodes.theme-danger jmexpander { 
   
}

jmnodes.theme-danger jmexpander:hover { 
   
}

/* success theme */
jmnodes.theme-success jmnode { 
   
  background-color: #5cb85c;
  border-color: #4cae4c;
  color: #fff;
}

jmnodes.theme-success jmnode:hover { 
   
  background-color: #47a447;
  border-color: #398439;
}

jmnodes.theme-success jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-success jmnode.root { 
   
}

jmnodes.theme-success jmexpander { 
   
}

jmnodes.theme-success jmexpander:hover { 
   
}

/* info theme */
jmnodes.theme-info jmnode { 
   
  background-color: #5dc0de;
  border-color: #46b8da;
  color: #fff;
}

jmnodes.theme-info jmnode:hover { 
   
  background-color: #39b3d7;
  border-color: #269abc;
}

jmnodes.theme-info jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-info jmnode.root { 
   
}

jmnodes.theme-info jmexpander { 
   
}

jmnodes.theme-info jmexpander:hover { 
   
}

/* greensea theme */
jmnodes.theme-greensea jmnode { 
   
  background-color: #1abc9c;
  color: #fff;
}

jmnodes.theme-greensea jmnode:hover { 
   
  background-color: #16a085;
}

jmnodes.theme-greensea jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-greensea jmnode.root { 
   
}

jmnodes.theme-greensea jmexpander { 
   
}

jmnodes.theme-greensea jmexpander:hover { 
   
}

/* nephrite theme */
jmnodes.theme-nephrite jmnode { 
   
  background-color: #2ecc71;
  color: #fff;
}

jmnodes.theme-nephrite jmnode:hover { 
   
  background-color: #27ae60;
}

jmnodes.theme-nephrite jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-nephrite jmnode.root { 
   
}

jmnodes.theme-nephrite jmexpander { 
   
}

jmnodes.theme-nephrite jmexpander:hover { 
   
}

/* belizehole theme */
jmnodes.theme-belizehole jmnode { 
   
  background-color: #3498db;
  color: #fff;
}

jmnodes.theme-belizehole jmnode:hover { 
   
  background-color: #2980b9;
}

jmnodes.theme-belizehole jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-belizehole jmnode.root { 
   
}

jmnodes.theme-belizehole jmexpander { 
   
}

jmnodes.theme-belizehole jmexpander:hover { 
   
}

/* wisteria theme */
jmnodes.theme-wisteria jmnode { 
   
  background-color: #9b59b6;
  color: #fff;
}

jmnodes.theme-wisteria jmnode:hover { 
   
  background-color: #8e44ad;
}

jmnodes.theme-wisteria jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-wisteria jmnode.root { 
   
}

jmnodes.theme-wisteria jmexpander { 
   
}

jmnodes.theme-wisteria jmexpander:hover { 
   
}

/* asphalt theme */
jmnodes.theme-asphalt jmnode { 
   
  background-color: #34495e;
  color: #fff;
}

jmnodes.theme-asphalt jmnode:hover { 
   
  background-color: #2c3e50;
}

jmnodes.theme-asphalt jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-asphalt jmnode.root { 
   
}

jmnodes.theme-asphalt jmexpander { 
   
}

jmnodes.theme-asphalt jmexpander:hover { 
   
}

/* orange theme */
jmnodes.theme-orange jmnode { 
   
  background-color: #f1c40f;
  color: #fff;
}

jmnodes.theme-orange jmnode:hover { 
   
  background-color: #f39c12;
}

jmnodes.theme-orange jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-orange jmnode.root { 
   
}

jmnodes.theme-orange jmexpander { 
   
}

jmnodes.theme-orange jmexpander:hover { 
   
}

/* pumpkin theme */
jmnodes.theme-pumpkin jmnode { 
   
  background-color: #e67e22;
  color: #fff;
}

jmnodes.theme-pumpkin jmnode:hover { 
   
  background-color: #d35400;
}

jmnodes.theme-pumpkin jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-pumpkin jmnode.root { 
   
}

jmnodes.theme-pumpkin jmexpander { 
   
}

jmnodes.theme-pumpkin jmexpander:hover { 
   
}

/* pomegranate theme */
jmnodes.theme-pomegranate jmnode { 
   
  background-color: #e74c3c;
  color: #fff;
}

jmnodes.theme-pomegranate jmnode:hover { 
   
  background-color: #c0392b;
}

jmnodes.theme-pomegranate jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-pomegranate jmnode.root { 
   
}

jmnodes.theme-pomegranate jmexpander { 
   
}

jmnodes.theme-pomegranate jmexpander:hover { 
   
}

/* clouds theme */
jmnodes.theme-clouds jmnode { 
   
  background-color: #ecf0f1;
  color: #333;
}

jmnodes.theme-clouds jmnode:hover { 
   
  background-color: #bdc3c7;
}

jmnodes.theme-clouds jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-clouds jmnode.root { 
   
}

jmnodes.theme-clouds jmexpander { 
   
}

jmnodes.theme-clouds jmexpander:hover { 
   
}

/* asbestos theme */
jmnodes.theme-asbestos jmnode { 
   
  background-color: #95a5a6;
  color: #fff;
}

jmnodes.theme-asbestos jmnode:hover { 
   
  background-color: #7f8c8d;
}

jmnodes.theme-asbestos jmnode.selected { 
   
  background-color: #11f;
  color: #fff;
}

jmnodes.theme-asbestos jmnode.root { 
   
}

jmnodes.theme-asbestos jmexpander { 
   
}

jmnodes.theme-asbestos jmexpander:hover { 
   
}

4. jsmind.js


/* * Released under BSD License * Copyright (c) 2014-2016 hizzgdev@163.com * * Project Home: * https://github.com/hizzgdev/jsmind/ */

;(function ($w) { 
   
    'use strict';
    // set 'jsMind' as the library name.
    // __name__ should be a const value, Never try to change it easily.
    var __name__ = 'jsMind';
    // library version
    var __version__ = '0.4.6';
    // author
    var __author__ = 'hizzgdev@163.com';

    // an noop function define
    var _noop = function () { 
   
    };
    var logger = (typeof console === 'undefined') ? { 
   
        log: _noop, debug: _noop, error: _noop, warn: _noop, info: _noop
    } : console;

    // check global variables
    if (typeof module === 'undefined' || !module.exports) { 
   
        if (typeof $w[__name__] != 'undefined') { 
   
            logger.log(__name__ + ' has been already exist.');
            return;
        }
    }

    // shortcut of methods in dom
    var $d = $w.document;
    var $g = function (id) { 
   
        return $d.getElementById(id);
    };
    var $c = function (tag) { 
   
        return $d.createElement(tag);
    };
    var $t = function (n, t) { 
   
        if (n.hasChildNodes()) { 
   
            n.firstChild.nodeValue = t;
        } else { 
   
            n.appendChild($d.createTextNode(t));
        }
    };

    var $h = function (n, t) { 
   
        if (t instanceof HTMLElement) { 
   
            n.innerHTML = '';
            n.appendChild(t)
        } else { 
   
            n.innerHTML = t;
        }
    };
    // detect isElement
    var $i = function (el) { 
   
        return !!el && (typeof el === 'object') && (el.nodeType === 1) && (typeof el.style === 'object') && (typeof el.ownerDocument === 'object');
    };
    if (typeof String.prototype.startsWith != 'function') { 
   
        String.prototype.startsWith = function (p) { 
   
            return this.slice(0, p.length) === p;
        };
    }

    var DEFAULT_OPTIONS = { 
   
        container: '',   // id of the container
        editable: false, // you can change it in your options
        theme: null,
        mode: 'full',     // full or side
        support_html: true,

        view: { 
   
            hmargin: 100,
            vmargin: 50,
            line_width: 2,
            line_color: '#555'
        },
        layout: { 
   
            hspace: 30,
            vspace: 20,
            pspace: 13
        },
        default_event_handle: { 
   
            enable_mousedown_handle: true,
            enable_click_handle: true,
            enable_dblclick_handle: true
        },
        shortcut: { 
   
            enable: true,
            handles: { 
   },
            mapping: { 
   
                addchild: 45, // Insert
                addbrother: 13, // Enter
                editnode: 113,// F2
                delnode: 46, // Delete
                toggle: 32, // Space
                left: 37, // Left
                up: 38, // Up
                right: 39, // Right
                down: 40, // Down
            }
        },
    };

    // core object
    var jm = function (options) { 
   
        jm.current = this;

        this.version = __version__;
        var opts = { 
   };
        jm.util.json.merge(opts, DEFAULT_OPTIONS);
        jm.util.json.merge(opts, options);

        if (!opts.container) { 
   
            logger.error('the options.container should not be null or empty.');
            return;
        }
        this.options = opts;
        this.inited = false;
        this.mind = null;
        this.event_handles = [];
        this.init();
    };

    // ============= static object =============================================
    jm.direction = { 
   left: -1, center: 0, right: 1};
    jm.event_type = { 
   show: 1, resize: 2, edit: 3, select: 4};

    jm.node = function (sId, iIndex, sTopic, oData, bIsRoot, oParent, eDirection, bExpanded) { 
   
        if (!sId) { 
   
            logger.error('invalid nodeid');
            return;
        }
        if (typeof iIndex != 'number') { 
   
            logger.error('invalid node index');
            return;
        }
        if (typeof bExpanded === 'undefined') { 
   
            bExpanded = true;
        }
        this.id = sId;
        this.index = iIndex;
        this.topic = sTopic;
        this.data = oData || { 
   };
        this.isroot = bIsRoot;
        this.parent = oParent;
        this.direction = eDirection;
        this.expanded = !!bExpanded;
        this.children = [];
        this._data = { 
   };
    };

    jm.node.compare = function (node1, node2) { 
   
        // '-1' is alwary the last
        var r = 0;
        var i1 = node1.index;
        var i2 = node2.index;
        if (i1 >= 0 && i2 >= 0) { 
   
            r = i1 - i2;
        } else if (i1 == -1 && i2 == -1) { 
   
            r = 0;
        } else if (i1 == -1) { 
   
            r = 1;
        } else if (i2 == -1) { 
   
            r = -1;
        } else { 
   
            r = 0;
        }
        //logger.debug(i1+' <> '+i2+' = '+r);
        return r;
    };

    jm.node.inherited = function (pnode, node) { 
   
        if (!!pnode && !!node) { 
   
            if (pnode.id === node.id) { 
   
                return true;
            }
            if (pnode.isroot) { 
   
                return true;
            }
            var pid = pnode.id;
            var p = node;
            while (!p.isroot) { 
   
                p = p.parent;
                if (p.id === pid) { 
   
                    return true;
                }
            }
        }
        return false;
    };

    jm.node.prototype = { 
   
        get_location: function () { 
   
            var vd = this._data.view;
            console.log('vd',vd);
            return { 
   
                x: vd.abs_x,
                y: vd.abs_y
            };
        },
        get_size: function () { 
   
            var vd = this._data.view;
            return { 
   
                w: vd.width,
                h: vd.height
            }
        }
    };


    jm.mind = function () { 
   
        this.name = null;
        this.author = null;
        this.version = null;
        this.root = null;
        this.selected = null;
        this.nodes = { 
   };
    };

    jm.mind.prototype = { 
   
        get_node: function (nodeid) { 
   
            if (nodeid in this.nodes) { 
   
                return this.nodes[nodeid];
            } else { 
   
                logger.warn('the node[id=' + nodeid + '] can not be found');
                return null;
            }
        },

        set_root: function (nodeid, topic, data) { 
   
            if (this.root == null) { 
   
                this.root = new jm.node(nodeid, 0, topic, data, true);
                this._put_node(this.root);
            } else { 
   
                logger.error('root node is already exist');
            }
        },

        add_node: function (parent_node, nodeid, topic, data, idx, direction, expanded) { 
   
            if (!jm.util.is_node(parent_node)) { 
   
                var the_parent_node = this.get_node(parent_node);
                if (!the_parent_node) { 
   
                    logger.error('the parent_node[id=' + parent_node + '] can not be found.');
                    return null;
                } else { 
   
                    return this.add_node(the_parent_node, nodeid, topic, data, idx, direction, expanded);
                }
            }
            var nodeindex = idx || -1;
            var node = null;
            if (parent_node.isroot) { 
   
                var d = jm.direction.right;
                if (isNaN(direction)) { 
   
                    var children = parent_node.children;
                    var children_len = children.length;
                    var r = 0;
                    for (var i = 0; i < children_len; i++) { 
   
                        if (children[i].direction === jm.direction.left) { 
   
                            r--;
                        } else { 
   
                            r++;
                        }
                    }
                    d = (children_len > 1 && r > 0) ? jm.direction.left : jm.direction.right
                } else { 
   
                    d = (direction != jm.direction.left) ? jm.direction.right : jm.direction.left;
                }
                node = new jm.node(nodeid, nodeindex, topic, data, false, parent_node, d, expanded);
            } else { 
   
                node = new jm.node(nodeid, nodeindex, topic, data, false, parent_node, parent_node.direction, expanded);
            }
            if (this._put_node(node)) { 
   
                parent_node.children.push(node);
                this._reindex(parent_node);
            } else { 
   
                logger.error('fail, the nodeid \'' + node.id + '\' has been already exist.');
                node = null;
            }
            return node;
        },

        insert_node_before: function (node_before, nodeid, topic, data) { 
   
            if (!jm.util.is_node(node_before)) { 
   
                var the_node_before = this.get_node(node_before);
                if (!the_node_before) { 
   
                    logger.error('the node_before[id=' + node_before + '] can not be found.');
                    return null;
                } else { 
   
                    return this.insert_node_before(the_node_before, nodeid, topic, data);
                }
            }
            var node_index = node_before.index - 0.5;
            return this.add_node(node_before.parent, nodeid, topic, data, node_index);
        },

        get_node_before: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return null;
                } else { 
   
                    return this.get_node_before(the_node);
                }
            }
            if (node.isroot) { 
   
                return null;
            }
            var idx = node.index - 2;
            if (idx >= 0) { 
   
                return node.parent.children[idx];
            } else { 
   
                return null;
            }
        },

        insert_node_after: function (node_after, nodeid, topic, data) { 
   
            if (!jm.util.is_node(node_after)) { 
   
                var the_node_after = this.get_node(node_before);
                if (!the_node_after) { 
   
                    logger.error('the node_after[id=' + node_after + '] can not be found.');
                    return null;
                } else { 
   
                    return this.insert_node_after(the_node_after, nodeid, topic, data);
                }
            }
            var node_index = node_after.index + 0.5;
            return this.add_node(node_after.parent, nodeid, topic, data, node_index);
        },

        get_node_after: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return null;
                } else { 
   
                    return this.get_node_after(the_node);
                }
            }
            if (node.isroot) { 
   
                return null;
            }
            var idx = node.index;
            var brothers = node.parent.children;
            if (brothers.length >= idx) { 
   
                return node.parent.children[idx];
            } else { 
   
                return null;
            }
        },

        move_node: function (node, beforeid, parentid, direction) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return null;
                } else { 
   
                    return this.move_node(the_node, beforeid, parentid, direction);
                }
            }
            if (!parentid) { 
   
                parentid = node.parent.id;
            }
            return this._move_node(node, beforeid, parentid, direction);
        },

        _flow_node_direction: function (node, direction) { 
   
            if (typeof direction === 'undefined') { 
   
                direction = node.direction;
            } else { 
   
                node.direction = direction;
            }
            var len = node.children.length;
            while (len--) { 
   
                this._flow_node_direction(node.children[len], direction);
            }
        },

        _move_node_internal: function (node, beforeid) { 
   
            if (!!node && !!beforeid) { 
   
                if (beforeid == '_last_') { 
   
                    node.index = -1;
                    this._reindex(node.parent);
                } else if (beforeid == '_first_') { 
   
                    node.index = 0;
                    this._reindex(node.parent);
                } else { 
   
                    var node_before = (!!beforeid) ? this.get_node(beforeid) : null;
                    if (node_before != null && node_before.parent != null && node_before.parent.id == node.parent.id) { 
   
                        node.index = node_before.index - 0.5;
                        this._reindex(node.parent);
                    }
                }
            }
            return node;
        },

        _move_node: function (node, beforeid, parentid, direction) { 
   
            if (!!node && !!parentid) { 
   
                if (node.parent.id != parentid) { 
   
                    // remove from parent's children
                    var sibling = node.parent.children;
                    var si = sibling.length;
                    while (si--) { 
   
                        if (sibling[si].id == node.id) { 
   
                            sibling.splice(si, 1);
                            break;
                        }
                    }
                    node.parent = this.get_node(parentid);
                    node.parent.children.push(node);
                }

                if (node.parent.isroot) { 
   
                    if (direction == jsMind.direction.left) { 
   
                        node.direction = direction;
                    } else { 
   
                        node.direction = jm.direction.right;
                    }
                } else { 
   
                    node.direction = node.parent.direction;
                }
                this._move_node_internal(node, beforeid);
                this._flow_node_direction(node);
            }
            return node;
        },

        remove_node: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return false;
                } else { 
   
                    return this.remove_node(the_node);
                }
            }
            if (!node) { 
   
                logger.error('fail, the node can not be found');
                return false;
            }
            if (node.isroot) { 
   
                logger.error('fail, can not remove root node');
                return false;
            }
            if (this.selected != null && this.selected.id == node.id) { 
   
                this.selected = null;
            }
            // clean all subordinate nodes
            var children = node.children;
            var ci = children.length;
            while (ci--) { 
   
                this.remove_node(children[ci]);
            }
            // clean all children
            children.length = 0;
            // remove from parent's children
            var sibling = node.parent.children;
            var si = sibling.length;
            while (si--) { 
   
                if (sibling[si].id == node.id) { 
   
                    sibling.splice(si, 1);
                    break;
                }
            }
            // remove from global nodes
            delete this.nodes[node.id];
            // clean all properties
            for (var k in node) { 
   
                delete node[k];
            }
            // remove it's self
            node = null;
            //delete node;
            return true;
        },

        _put_node: function (node) { 
   
            if (node.id in this.nodes) { 
   
                logger.warn('the nodeid \'' + node.id + '\' has been already exist.');
                return false;
            } else { 
   
                this.nodes[node.id] = node;
                return true;
            }
        },

        _reindex: function (node) { 
   
            if (node instanceof jm.node) { 
   
                node.children.sort(jm.node.compare);
                for (var i = 0; i < node.children.length; i++) { 
   
                    node.children[i].index = i + 1;
                }
            }
        },
    };

    jm.format = { 
   
        node_tree: { 
   
            example: { 
   
                "meta": { 
   
                    "name": __name__,
                    "author": __author__,
                    "version": __version__
                },
                "format": "node_tree",
                "data": { 
   "id": "root", "topic": "jsMind Example"}
            },
            get_mind: function (source) { 
   
                var df = jm.format.node_tree;
                var mind = new jm.mind();
                mind.name = source.meta.name;
                mind.author = source.meta.author;
                mind.version = source.meta.version;
                df._parse(mind, source.data);
                return mind;
            },
            get_data: function (mind) { 
   
                var df = jm.format.node_tree;
                var json = { 
   };
                json.meta = { 
   
                    name: mind.name,
                    author: mind.author,
                    version: mind.version
                };
                json.format = 'node_tree';
                json.data = df._buildnode(mind.root);
                return json;
            },

            _parse: function (mind, node_root) { 
   
                var df = jm.format.node_tree;
                var data = df._extract_data(node_root);
                mind.set_root(node_root.id, node_root.topic, data);
                if ('children' in node_root) { 
   
                    var children = node_root.children;
                    for (var i = 0; i < children.length; i++) { 
   
                        df._extract_subnode(mind, mind.root, children[i]);
                    }
                }
            },

            _extract_data: function (node_json) { 
   
                var data = { 
   };
                for (var k in node_json) { 
   
                    if (k == 'id' || k == 'topic' || k == 'children' || k == 'direction' || k == 'expanded') { 
   
                        continue;
                    }
                    data[k] = node_json[k];
                }
                return data;
            },

            _extract_subnode: function (mind, node_parent, node_json) { 
   
                var df = jm.format.node_tree;
                var data = df._extract_data(node_json);
                var d = null;
                if (node_parent.isroot) { 
   
                    d = node_json.direction == 'left' ? jm.direction.left : jm.direction.right;
                }
                var node = mind.add_node(node_parent, node_json.id, node_json.topic, data, null, d, node_json.expanded);
                if ('children' in node_json) { 
   
                    var children = node_json.children;
                    for (var i = 0; i < children.length; i++) { 
   
                        df._extract_subnode(mind, node, children[i]);
                    }
                }
            },

            _buildnode: function (node) { 
   
                var df = jm.format.node_tree;
                if (!(node instanceof jm.node)) { 
   
                    return;
                }
                var o = { 
   
                    id: node.id,
                    topic: node.topic,
                    expanded: node.expanded
                };
                if (!!node.parent && node.parent.isroot) { 
   
                    o.direction = node.direction == jm.direction.left ? 'left' : 'right';
                }
                if (node.data != null) { 
   
                    var node_data = node.data;
                    for (var k in node_data) { 
   
                        o[k] = node_data[k];
                    }
                }
                var children = node.children;
                if (children.length > 0) { 
   
                    o.children = [];
                    for (var i = 0; i < children.length; i++) { 
   
                        o.children.push(df._buildnode(children[i]));
                    }
                }
                return o;
            }
        },

        node_array: { 
   
            example: { 
   
                "meta": { 
   
                    "name": __name__,
                    "author": __author__,
                    "version": __version__
                },
                "format": "node_array",
                "data": [
                    { 
   "id": "root", "topic": "jsMind Example", "isroot": true}
                ]
            },

            get_mind: function (source) { 
   
                var df = jm.format.node_array;
                var mind = new jm.mind();
                mind.name = source.meta.name;
                mind.author = source.meta.author;
                mind.version = source.meta.version;
                df._parse(mind, source.data);
                return mind;
            },

            get_data: function (mind) { 
   
                var df = jm.format.node_array;
                var json = { 
   };
                json.meta = { 
   
                    name: mind.name,
                    author: mind.author,
                    version: mind.version
                };
                json.format = 'node_array';
                json.data = [];
                df._array(mind, json.data);
                return json;
            },

            _parse: function (mind, node_array) { 
   
                var df = jm.format.node_array;
                var narray = node_array.slice(0);
                // reverse array for improving looping performance
                narray.reverse();
                var root_id = df._extract_root(mind, narray);
                if (!!root_id) { 
   
                    df._extract_subnode(mind, root_id, narray);
                } else { 
   
                    logger.error('root node can not be found');
                }
            },

            _extract_root: function (mind, node_array) { 
   
                var df = jm.format.node_array;
                var i = node_array.length;
                while (i--) { 
   
                    if ('isroot' in node_array[i] && node_array[i].isroot) { 
   
                        var root_json = node_array[i];
                        var data = df._extract_data(root_json);
                        mind.set_root(root_json.id, root_json.topic, data);
                        node_array.splice(i, 1);
                        return root_json.id;
                    }
                }
                return null;
            },

            _extract_subnode: function (mind, parentid, node_array) { 
   
                var df = jm.format.node_array;
                var i = node_array.length;
                var node_json = null;
                var data = null;
                var extract_count = 0;
                while (i--) { 
   
                    node_json = node_array[i];
                    if (node_json.parentid == parentid) { 
   
                        data = df._extract_data(node_json);
                        var d = null;
                        var node_direction = node_json.direction;
                        if (!!node_direction) { 
   
                            d = node_direction == 'left' ? jm.direction.left : jm.direction.right;
                        }
                        mind.add_node(parentid, node_json.id, node_json.topic, data, null, d, node_json.expanded);
                        node_array.splice(i, 1);
                        extract_count++;
                        var sub_extract_count = df._extract_subnode(mind, node_json.id, node_array);
                        if (sub_extract_count > 0) { 
   
                            // reset loop index after extract subordinate node
                            i = node_array.length;
                            extract_count += sub_extract_count;
                        }
                    }
                }
                return extract_count;
            },

            _extract_data: function (node_json) { 
   
                var data = { 
   };
                for (var k in node_json) { 
   
                    if (k == 'id' || k == 'topic' || k == 'parentid' || k == 'isroot' || k == 'direction' || k == 'expanded') { 
   
                        continue;
                    }
                    data[k] = node_json[k];
                }
                return data;
            },

            _array: function (mind, node_array) { 
   
                var df = jm.format.node_array;
                df._array_node(mind.root, node_array);
            },

            _array_node: function (node, node_array) { 
   
                var df = jm.format.node_array;
                if (!(node instanceof jm.node)) { 
   
                    return;
                }
                var o = { 
   
                    id: node.id,
                    topic: node.topic,
                    expanded: node.expanded
                };
                if (!!node.parent) { 
   
                    o.parentid = node.parent.id;
                }
                if (node.isroot) { 
   
                    o.isroot = true;
                }
                if (!!node.parent && node.parent.isroot) { 
   
                    o.direction = node.direction == jm.direction.left ? 'left' : 'right';
                }
                if (node.data != null) { 
   
                    var node_data = node.data;
                    for (var k in node_data) { 
   
                        o[k] = node_data[k];
                    }
                }
                node_array.push(o);
                var ci = node.children.length;
                for (var i = 0; i < ci; i++) { 
   
                    df._array_node(node.children[i], node_array);
                }
            },
        },

        freemind: { 
   
            example: { 
   
                "meta": { 
   
                    "name": __name__,
                    "author": __author__,
                    "version": __version__
                },
                "format": "freemind",
                "data": "<map version=\"1.0.1\"><node ID=\"root\" TEXT=\"freemind Example\"/></map>"
            },
            get_mind: function (source) { 
   
                var df = jm.format.freemind;
                var mind = new jm.mind();
                mind.name = source.meta.name;
                mind.author = source.meta.author;
                mind.version = source.meta.version;
                var xml = source.data;
                var xml_doc = df._parse_xml(xml);
                var xml_root = df._find_root(xml_doc);
                df._load_node(mind, null, xml_root);
                return mind;
            },

            get_data: function (mind) { 
   
                var df = jm.format.freemind;
                var json = { 
   };
                json.meta = { 
   
                    name: mind.name,
                    author: mind.author,
                    version: mind.version
                };
                json.format = 'freemind';
                var xmllines = [];
                xmllines.push('<map version=\"1.0.1\">');
                df._buildmap(mind.root, xmllines);
                xmllines.push('</map>');
                json.data = xmllines.join(' ');
                return json;
            },

            _parse_xml: function (xml) { 
   
                var xml_doc = null;
                if (window.DOMParser) { 
   
                    var parser = new DOMParser();
                    xml_doc = parser.parseFromString(xml, 'text/xml');
                } else { 
    // Internet Explorer
                    xml_doc = new ActiveXObject('Microsoft.XMLDOM');
                    xml_doc.async = false;
                    xml_doc.loadXML(xml);
                }
                return xml_doc;
            },

            _find_root: function (xml_doc) { 
   
                var nodes = xml_doc.childNodes;
                var node = null;
                var root = null;
                var n = null;
                for (var i = 0; i < nodes.length; i++) { 
   
                    n = nodes[i];
                    if (n.nodeType == 1 && n.tagName == 'map') { 
   
                        node = n;
                        break;
                    }
                }
                if (!!node) { 
   
                    var ns = node.childNodes;
                    node = null;
                    for (var i = 0; i < ns.length; i++) { 
   
                        n = ns[i];
                        if (n.nodeType == 1 && n.tagName == 'node') { 
   
                            node = n;
                            break;
                        }
                    }
                }
                return node;
            },

            _load_node: function (mind, parent_id, xml_node) { 
   
                var df = jm.format.freemind;
                var node_id = xml_node.getAttribute('ID');
                var node_topic = xml_node.getAttribute('TEXT');
                // look for richcontent
                if (node_topic == null) { 
   
                    var topic_children = xml_node.childNodes;
                    var topic_child = null;
                    for (var i = 0; i < topic_children.length; i++) { 
   
                        topic_child = topic_children[i];
                        //logger.debug(topic_child.tagName);
                        if (topic_child.nodeType == 1 && topic_child.tagName === 'richcontent') { 
   
                            node_topic = topic_child.textContent;
                            break;
                        }
                    }
                }
                var node_data = df._load_attributes(xml_node);
                var node_expanded = ('expanded' in node_data) ? (node_data.expanded == 'true') : true;
                delete node_data.expanded;

                var node_position = xml_node.getAttribute('POSITION');
                var node_direction = null;
                if (!!node_position) { 
   
                    node_direction = node_position == 'left' ? jm.direction.left : jm.direction.right;
                }
                //logger.debug(node_position +':'+ node_direction);
                if (!!parent_id) { 
   
                    mind.add_node(parent_id, node_id, node_topic, node_data, null, node_direction, node_expanded);
                } else { 
   
                    mind.set_root(node_id, node_topic, node_data);
                }
                var children = xml_node.childNodes;
                var child = null;
                for (var i = 0; i < children.length; i++) { 
   
                    child = children[i];
                    if (child.nodeType == 1 && child.tagName == 'node') { 
   
                        df._load_node(mind, node_id, child);
                    }
                }
            },

            _load_attributes: function (xml_node) { 
   
                var children = xml_node.childNodes;
                var attr = null;
                var attr_data = { 
   };
                for (var i = 0; i < children.length; i++) { 
   
                    attr = children[i];
                    if (attr.nodeType == 1 && attr.tagName === 'attribute') { 
   
                        attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE');
                    }
                }
                return attr_data;
            },

            _buildmap: function (node, xmllines) { 
   
                var df = jm.format.freemind;
                var pos = null;
                if (!!node.parent && node.parent.isroot) { 
   
                    pos = node.direction === jm.direction.left ? 'left' : 'right';
                }
                xmllines.push('<node');
                xmllines.push('ID=\"' + node.id + '\"');
                if (!!pos) { 
   
                    xmllines.push('POSITION=\"' + pos + '\"');
                }
                xmllines.push('TEXT=\"' + node.topic + '\">');

                // store expanded status as an attribute
                xmllines.push('<attribute NAME=\"expanded\" VALUE=\"' + node.expanded + '\"/>');

                // for attributes
                var node_data = node.data;
                if (node_data != null) { 
   
                    for (var k in node_data) { 
   
                        xmllines.push('<attribute NAME=\"' + k + '\" VALUE=\"' + node_data[k] + '\"/>');
                    }
                }

                // for children
                var children = node.children;
                for (var i = 0; i < children.length; i++) { 
   
                    df._buildmap(children[i], xmllines);
                }

                xmllines.push('</node>');
            },
        },
    };

    // ============= utility object =============================================

    jm.util = { 
   
        is_node: function (node) { 
   
            return !!node && node instanceof jm.node;
        },
        ajax: { 
   
            _xhr: function () { 
   
                var xhr = null;
                if (window.XMLHttpRequest) { 
   
                    xhr = new XMLHttpRequest();
                } else { 
   
                    try { 
   
                        xhr = new ActiveXObject('Microsoft.XMLHTTP');
                    } catch (e) { 
   
                    }
                }
                return xhr;
            },
            _eurl: function (url) { 
   
                return encodeURIComponent(url);
            },
            request: function (url, param, method, callback, fail_callback) { 
   
                var a = jm.util.ajax;
                var p = null;
                var tmp_param = [];
                for (var k in param) { 
   
                    tmp_param.push(a._eurl(k) + '=' + a._eurl(param[k]));
                }
                if (tmp_param.length > 0) { 
   
                    p = tmp_param.join('&');
                }
                var xhr = a._xhr();
                if (!xhr) { 
   
                    return;
                }
                xhr.onreadystatechange = function () { 
   
                    if (xhr.readyState == 4) { 
   
                        if (xhr.status == 200 || xhr.status == 0) { 
   
                            if (typeof callback === 'function') { 
   
                                var data = jm.util.json.string2json(xhr.responseText);
                                if (data != null) { 
   
                                    callback(data);
                                } else { 
   
                                    callback(xhr.responseText);
                                }
                            }
                        } else { 
   
                            if (typeof fail_callback === 'function') { 
   
                                fail_callback(xhr);
                            } else { 
   
                                logger.error('xhr request failed.', xhr);
                            }
                        }
                    }
                }
                method = method || 'GET';
                xhr.open(method, url, true);
                xhr.setRequestHeader('If-Modified-Since', '0');
                if (method == 'POST') { 
   
                    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
                    xhr.send(p);
                } else { 
   
                    xhr.send();
                }
            },
            get: function (url, callback) { 
   
                return jm.util.ajax.request(url, { 
   }, 'GET', callback);
            },
            post: function (url, param, callback) { 
   
                return jm.util.ajax.request(url, param, 'POST', callback);
            }
        },

        dom: { 
   
            //target,eventType,handler
            add_event: function (t, e, h) { 
   
                if (!!t.addEventListener) { 
   
                    t.addEventListener(e, h, false);
                } else { 
   
                    t.attachEvent('on' + e, h);
                }
            }
        },

        canvas: { 
   
            bezierto: function (ctx, x1, y1, x2, y2) { 
   
                ctx.beginPath();
                ctx.moveTo(x1, y1);
                ctx.bezierCurveTo(x1 + (x2 - x1) * 2 / 3, y1, x1, y2, x2, y2);
                ctx.stroke();
            },
            lineto: function (ctx, x1, y1, x2, y2) { 
   
                ctx.beginPath();
                ctx.moveTo(x1, y1);
                ctx.lineTo(x2, y2);
                ctx.stroke();
            },
            clear: function (ctx, x, y, w, h) { 
   
                ctx.clearRect(x, y, w, h);
            }
        },

        file: { 
   
            read: function (file_data, fn_callback) { 
   
                var reader = new FileReader();
                reader.onload = function () { 
   
                    if (typeof fn_callback === 'function') { 
   
                        fn_callback(this.result, file_data.name);
                    }
                };
                reader.readAsText(file_data);
            },

            save: function (file_data, type, name) { 
   
                var blob;
                if (typeof $w.Blob === 'function') { 
   
                    blob = new Blob([file_data], { 
   type: type});
                } else { 
   
                    var BlobBuilder = $w.BlobBuilder || $w.MozBlobBuilder || $w.WebKitBlobBuilder || $w.MSBlobBuilder;
                    var bb = new BlobBuilder();
                    bb.append(file_data);
                    blob = bb.getBlob(type);
                }
                if (navigator.msSaveBlob) { 
   
                    navigator.msSaveBlob(blob, name);
                } else { 
   
                    var URL = $w.URL || $w.webkitURL;
                    var bloburl = URL.createObjectURL(blob);
                    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;
                    }
                }
            }
        },

        json: { 
   
            json2string: function (json) { 
   
                if (!!JSON) { 
   
                    try { 
   
                        var json_str = JSON.stringify(json);
                        return json_str;
                    } catch (e) { 
   
                        logger.warn(e);
                        logger.warn('can not convert to string');
                        return null;
                    }
                }
            },
            string2json: function (json_str) { 
   
                if (!!JSON) { 
   
                    try { 
   
                        var json = JSON.parse(json_str);
                        return json;
                    } catch (e) { 
   
                        logger.warn(e);
                        logger.warn('can not parse to json');
                        return null;
                    }
                }
            },
            merge: function (b, a) { 
   
                for (var o in a) { 
   
                    if (o in b) { 
   
                        if (typeof b[o] === 'object' &&
                            Object.prototype.toString.call(b[o]).toLowerCase() == '[object object]' &&
                            !b[o].length) { 
   
                            jm.util.json.merge(b[o], a[o]);
                        } else { 
   
                            b[o] = a[o];
                        }
                    } else { 
   
                        b[o] = a[o];
                    }
                }
                return b;
            }
        },

        uuid: { 
   
            newid: function () { 
   
                return (new Date().getTime().toString(16) + Math.random().toString(16).substr(2)).substr(2, 16);
            }
        },

        text: { 
   
            is_empty: function (s) { 
   
                if (!s) { 
   
                    return true;
                }
                return s.replace(/\s*/, '').length == 0;
            }
        }
    };

    jm.prototype = { 
   
        init: function () { 
   
            if (this.inited) { 
   
                return;
            }
            this.inited = true;

            var opts = this.options;

            var opts_layout = { 
   
                mode: opts.mode,
                hspace: opts.layout.hspace,
                vspace: opts.layout.vspace,
                pspace: opts.layout.pspace
            }
            var opts_view = { 
   
                container: opts.container,
                support_html: opts.support_html,
                hmargin: opts.view.hmargin,
                vmargin: opts.view.vmargin,
                line_width: opts.view.line_width,
                line_color: opts.view.line_color
            };
            // create instance of function provider
            this.data = new jm.data_provider(this);
            this.layout = new jm.layout_provider(this, opts_layout);
            this.view = new jm.view_provider(this, opts_view);
            this.shortcut = new jm.shortcut_provider(this, opts.shortcut);

            this.data.init();
            this.layout.init();
            this.view.init();
            this.shortcut.init();

            this._event_bind();

            jm.init_plugins(this);
        },

        enable_edit: function () { 
   
            this.options.editable = true;
        },

        disable_edit: function () { 
   
            this.options.editable = false;
        },

        // call enable_event_handle('dblclick')
        // options are 'mousedown', 'click', 'dblclick'
        enable_event_handle: function (event_handle) { 
   
            this.options.default_event_handle['enable_' + event_handle + '_handle'] = true;
        },

        // call disable_event_handle('dblclick')
        // options are 'mousedown', 'click', 'dblclick'
        disable_event_handle: function (event_handle) { 
   
            this.options.default_event_handle['enable_' + event_handle + '_handle'] = false;
        },

        get_editable: function () { 
   
            return this.options.editable;
        },

        set_theme: function (theme) { 
   
            var theme_old = this.options.theme;
            this.options.theme = (!!theme) ? theme : null;
            if (theme_old != this.options.theme) { 
   
                this.view.reset_theme();
                this.view.reset_custom_style();
            }
        },
        _event_bind: function () { 
   
            this.view.add_event(this, 'mousedown', this.mousedown_handle);
            this.view.add_event(this, 'click', this.click_handle);
            this.view.add_event(this, 'dblclick', this.dblclick_handle);
        },

        mousedown_handle: function (e) { 
   
            if (!this.options.default_event_handle['enable_mousedown_handle']) { 
   
                return;
            }
            var element = e.target || event.srcElement;
            var nodeid = this.view.get_binded_nodeid(element);
            if (!!nodeid) { 
   
                this.select_node(nodeid);
            } else { 
   
                this.select_clear();
            }
        },

        click_handle: function (e) { 
   
            if (!this.options.default_event_handle['enable_click_handle']) { 
   
                return;
            }
            var element = e.target || event.srcElement;
            var isexpander = this.view.is_expander(element);
            if (isexpander) { 
   
                var nodeid = this.view.get_binded_nodeid(element);
                if (!!nodeid) { 
   
                    this.toggle_node(nodeid);
                }
            }
        },

        dblclick_handle: function (e) { 
   
            if (!this.options.default_event_handle['enable_dblclick_handle']) { 
   
                return;
            }
            if (this.get_editable()) { 
   
                var element = e.target || event.srcElement;
                var nodeid = this.view.get_binded_nodeid(element);
                if (!!nodeid) { 
   
                    this.begin_edit(nodeid);
                }
            }
        },

        begin_edit: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return false;
                } else { 
   
                    return this.begin_edit(the_node);
                }
            }
            if (this.get_editable()) { 
   
                this.view.edit_node_begin(node);
            } else { 
   
                logger.error('fail, this mind map is not editable.');
                return;
            }
        },

        end_edit: function () { 
   
            this.view.edit_node_end();
        },

        toggle_node: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return;
                } else { 
   
                    return this.toggle_node(the_node);
                }
            }
            if (node.isroot) { 
   
                return;
            }
            this.view.save_location(node);
            this.layout.toggle_node(node);
            this.view.relayout();
            this.view.restore_location(node);
        },

        expand_node: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return;
                } else { 
   
                    return this.expand_node(the_node);
                }
            }
            if (node.isroot) { 
   
                return;
            }
            this.view.save_location(node);
            this.layout.expand_node(node);
            this.view.relayout();
            this.view.restore_location(node);
        },

        collapse_node: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return;
                } else { 
   
                    return this.collapse_node(the_node);
                }
            }
            if (node.isroot) { 
   
                return;
            }
            this.view.save_location(node);
            this.layout.collapse_node(node);
            this.view.relayout();
            this.view.restore_location(node);
        },

        expand_all: function () { 
   
            this.layout.expand_all();
            this.view.relayout();
        },

        collapse_all: function () { 
   
            this.layout.collapse_all();
            this.view.relayout();
        },

        expand_to_depth: function (depth) { 
   
            this.layout.expand_to_depth(depth);
            this.view.relayout();
        },

        _reset: function () { 
   
            this.view.reset();
            this.layout.reset();
            this.data.reset();
        },

        _show: function (mind) { 
   
            var m = mind || jm.format.node_array.example;

            this.mind = this.data.load(m);
            if (!this.mind) { 
   
                logger.error('data.load error');
                return;
            } else { 
   
                logger.debug('data.load ok');
            }

            this.view.load();
            logger.debug('view.load ok');

            this.layout.layout();
            logger.debug('layout.layout ok');

            this.view.show(true);
            logger.debug('view.show ok');

            this.invoke_event_handle(jm.event_type.show, { 
   data: [mind]});
        },

        show: function (mind) { 
   
            this._reset();
            this._show(mind);
        },

        get_meta: function () { 
   
            return { 
   
                name: this.mind.name,
                author: this.mind.author,
                version: this.mind.version
            };
        },

        get_data: function (data_format) { 
   
            var df = data_format || 'node_tree';
            return this.data.get_data(df);
        },

        get_root: function () { 
   
            return this.mind.root;
        },

        get_node: function (nodeid) { 
   
            return this.mind.get_node(nodeid);
        },

        add_node: function (parent_node, nodeid, topic, data) { 
   
            if (this.get_editable()) { 
   
                var node = this.mind.add_node(parent_node, nodeid, topic, data);
                if (!!node) { 
   
                    this.view.add_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.view.reset_node_custom_style(node);
                    this.expand_node(parent_node);
                    this.invoke_event_handle(jm.event_type.edit, { 
   
                        evt: 'add_node',
                        data: [parent_node.id, nodeid, topic, data],
                        node: nodeid
                    });
                }
                return node;
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        insert_node_before: function (node_before, nodeid, topic, data) { 
   
            if (this.get_editable()) { 
   
                var beforeid = jm.util.is_node(node_before) ? node_before.id : node_before;
                var node = this.mind.insert_node_before(node_before, nodeid, topic, data);
                if (!!node) { 
   
                    this.view.add_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit, { 
   
                        evt: 'insert_node_before',
                        data: [beforeid, nodeid, topic, data],
                        node: nodeid
                    });
                }
                return node;
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        insert_node_after: function (node_after, nodeid, topic, data) { 
   
            if (this.get_editable()) { 
   
                var afterid = jm.util.is_node(node_after) ? node_after.id : node_after;
                var node = this.mind.insert_node_after(node_after, nodeid, topic, data);
                if (!!node) { 
   
                    this.view.add_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit, { 
   
                        evt: 'insert_node_after',
                        data: [afterid, nodeid, topic, data],
                        node: nodeid
                    });
                }
                return node;
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        remove_node: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return false;
                } else { 
   
                    return this.remove_node(the_node);
                }
            }
            if (this.get_editable()) { 
   
                if (node.isroot) { 
   
                    logger.error('fail, can not remove root node');
                    return false;
                }
                var nodeid = node.id;
                var parentid = node.parent.id;
                var parent_node = this.get_node(parentid);
                this.view.save_location(parent_node);
                this.view.remove_node(node);
                this.mind.remove_node(node);
                this.layout.layout();
                this.view.show(false);
                this.view.restore_location(parent_node);
                this.invoke_event_handle(jm.event_type.edit, { 
   evt: 'remove_node', data: [nodeid], node: parentid});
                return true;
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return false;
            }
        },

        update_node: function (nodeid, topic) { 
   
            if (this.get_editable()) { 
   
                if (jm.util.text.is_empty(topic)) { 
   
                    logger.warn('fail, topic can not be empty');
                    return;
                }
                var node = this.get_node(nodeid);
                if (!!node) { 
   
                    if (node.topic === topic) { 
   
                        logger.info('nothing changed');
                        this.view.update_node(node);
                        return;
                    }
                    node.topic = topic;
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit, { 
   
                        evt: 'update_node',
                        data: [nodeid, topic],
                        node: nodeid
                    });
                }
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return;
            }
        },

        move_node: function (nodeid, beforeid, parentid, direction) { 
   
            if (this.get_editable()) { 
   
                var node = this.mind.move_node(nodeid, beforeid, parentid, direction);
                if (!!node) { 
   
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit, { 
   
                        evt: 'move_node',
                        data: [nodeid, beforeid, parentid, direction],
                        node: nodeid
                    });
                }
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return;
            }
        },

        select_node: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return;
                } else { 
   
                    return this.select_node(the_node);
                }
            }
            if (!this.layout.is_visible(node)) { 
   
                return;
            }
            this.mind.selected = node;
            this.view.select_node(node);
        },

        get_selected_node: function () { 
   
            if (!!this.mind) { 
   
                return this.mind.selected;
            } else { 
   
                return null;
            }
        },

        select_clear: function () { 
   
            if (!!this.mind) { 
   
                this.mind.selected = null;
                this.view.select_clear();
            }
        },

        is_node_visible: function (node) { 
   
            return this.layout.is_visible(node);
        },

        find_node_before: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return;
                } else { 
   
                    return this.find_node_before(the_node);
                }
            }
            if (node.isroot) { 
   
                return null;
            }
            var n = null;
            if (node.parent.isroot) { 
   
                var c = node.parent.children;
                var prev = null;
                var ni = null;
                for (var i = 0; i < c.length; i++) { 
   
                    ni = c[i];
                    if (node.direction === ni.direction) { 
   
                        if (node.id === ni.id) { 
   
                            n = prev;
                        }
                        prev = ni;
                    }
                }
            } else { 
   
                n = this.mind.get_node_before(node);
            }
            return n;
        },

        find_node_after: function (node) { 
   
            if (!jm.util.is_node(node)) { 
   
                var the_node = this.get_node(node);
                if (!the_node) { 
   
                    logger.error('the node[id=' + node + '] can not be found.');
                    return;
                } else { 
   
                    return this.find_node_after(the_node);
                }
            }
            if (node.isroot) { 
   
                return null;
            }
            var n = null;
            if (node.parent.isroot) { 
   
                var c = node.parent.children;
                var getthis = false;
                var ni = null;
                for (var i = 0; i < c.length; i++) { 
   
                    ni = c[i];
                    if (node.direction === ni.direction) { 
   
                        if (getthis) { 
   
                            n = ni;
                            break;
                        }
                        if (node.id === ni.id) { 
   
                            getthis = true;
                        }
                    }
                }
            } else { 
   
                n = this.mind.get_node_after(node);
            }
            return n;
        },

        set_node_color: function (nodeid, bgcolor, fgcolor) { 
   
            if (this.get_editable()) { 
   
                var node = this.mind.get_node(nodeid);
                if (!!node) { 
   
                    if (!!bgcolor) { 
   
                        node.data['background-color'] = bgcolor;
                    }
                    if (!!fgcolor) { 
   
                        node.data['foreground-color'] = fgcolor;
                    }
                    this.view.reset_node_custom_style(node);
                }
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        set_node_font_style: function (nodeid, size, weight, style) { 
   
            if (this.get_editable()) { 
   
                var node = this.mind.get_node(nodeid);
                if (!!node) { 
   
                    if (!!size) { 
   
                        node.data['font-size'] = size;
                    }
                    if (!!weight) { 
   
                        node.data['font-weight'] = weight;
                    }
                    if (!!style) { 
   
                        node.data['font-style'] = style;
                    }
                    this.view.reset_node_custom_style(node);
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                }
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        set_node_background_image: function (nodeid, image, width, height, rotation) { 
   
            if (this.get_editable()) { 
   
                var node = this.mind.get_node(nodeid);
                if (!!node) { 
   
                    if (!!image) { 
   
                        node.data['background-image'] = image;
                    }
                    if (!!width) { 
   
                        node.data['width'] = width;
                    }
                    if (!!height) { 
   
                        node.data['height'] = height;
                    }
                    if (!!rotation) { 
   
                        node.data['background-rotation'] = rotation;
                    }
                    this.view.reset_node_custom_style(node);
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                }
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        set_node_background_rotation: function (nodeid, rotation) { 
   
            if (this.get_editable()) { 
   
                var node = this.mind.get_node(nodeid);
                if (!!node) { 
   
                    if (!node.data['background-image']) { 
   
                        logger.error('fail, only can change rotation angle of node with background image');
                        return null;
                    }
                    node.data['background-rotation'] = rotation;
                    this.view.reset_node_custom_style(node);
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                }
            } else { 
   
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        resize: function () { 
   
            this.view.resize();
        },

        // callback(type ,data)
        add_event_listener: function (callback) { 
   
            if (typeof callback === 'function') { 
   
                this.event_handles.push(callback);
            }
        },

        invoke_event_handle: function (type, data) { 
   
            var j = this;
            $w.setTimeout(function () { 
   
                j._invoke_event_handle(type, data);
            }, 0);
        },

        _invoke_event_handle: function (type, data) { 
   
            var l = this.event_handles.length;
            for (var i = 0; i < l; i++) { 
   
                this.event_handles[i](type, data);
            }
        }

    };

// ============= data provider =============================================

    jm.data_provider = function (jm) { 
   
        this.jm = jm;
    };

    jm.data_provider.prototype = { 
   
        init: function () { 
   
            logger.debug('data.init');
        },

        reset: function () { 
   
            logger.debug('data.reset');
        },

        load: function (mind_data) { 
   
            var df = null;
            var mind = null;
            if (typeof mind_data === 'object') { 
   
                if (!!mind_data.format) { 
   
                    df = mind_data.format;
                } else { 
   
                    df = 'node_tree';
                }
            } else { 
   
                df = 'freemind';
            }

            if (df == 'node_array') { 
   
                mind = jm.format.node_array.get_mind(mind_data);
            } else if (df == 'node_tree') { 
   
                mind = jm.format.node_tree.get_mind(mind_data);
            } else if (df == 'freemind') { 
   
                mind = jm.format.freemind.get_mind(mind_data);
            } else { 
   
                logger.warn('unsupported format');
            }
            return mind;
        },

        get_data: function (data_format) { 
   
            var data = null;
            if (data_format == 'node_array') { 
   
                data = jm.format.node_array.get_data(this.jm.mind);
            } else if (data_format == 'node_tree') { 
   
                data = jm.format.node_tree.get_data(this.jm.mind);
            } else if (data_format == 'freemind') { 
   
                data = jm.format.freemind.get_data(this.jm.mind);
            } else { 
   
                logger.error('unsupported ' + data_format + ' format');
            }
            return data;
        },
    };

    // ============= layout provider ===========================================

    jm.layout_provider = function (jm, options) { 
   
        this.opts = options;
        this.jm = jm;
        this.isside = (this.opts.mode == 'side');
        this.bounds = null;

        this.cache_valid = false;
    };

    jm.layout_provider.prototype = { 
   
        init: function () { 
   
            logger.debug('layout.init');
        },
        reset: function () { 
   
            logger.debug('layout.reset');
            this.bounds = { 
   n: 0, s: 0, w: 0, e: 0};
        },
        layout: function () { 
   
            logger.debug('layout.layout');
            this.layout_direction();
            this.layout_offset();
        },

        layout_direction: function () { 
   
            this._layout_direction_root();
        },

        _layout_direction_root: function () { 
   
            var node = this.jm.mind.root;
            // logger.debug(node);
            var layout_data = null;
            if ('layout' in node._data) { 
   
                layout_data = node._data.layout;
            } else { 
   
                layout_data = { 
   };
                node._data.layout = layout_data;
            }
            var children = node.children;
            var children_count = children.length;
            layout_data.direction = jm.direction.center;
            layout_data.side_index = 0;
            if (this.isside) { 
   
                var i = children_count;
                while (i--) { 
   
                    this._layout_direction_side(children[i], jm.direction.right, i);
                }
            } else { 
   
                var i = children_count;
                var subnode = null;
                while (i--) { 
   
                    subnode = children[i];
                    if (subnode.direction == jm.direction.left) { 
   
                        this._layout_direction_side(subnode, jm.direction.left, i);
                    } else { 
   
                        this._layout_direction_side(subnode, jm.direction.right, i);
                    }
                }
                /* var boundary = Math.ceil(children_count/2); var i = children_count; while(i--){ if(i>=boundary){ this._layout_direction_side(children[i],jm.direction.left, children_count-i-1); }else{ this._layout_direction_side(children[i],jm.direction.right, i); } }*/

            }
        },

        _layout_direction_side: function (node, direction, side_index) { 
   
            var layout_data = null;
            if ('layout' in node._data) { 
   
                layout_data = node._data.layout;
            } else { 
   
                layout_data = { 
   };
                node._data.layout = layout_data;
            }
            var children = node.children;
            var children_count = children.length;

            layout_data.direction = direction;
            layout_data.side_index = side_index;
            var i = children_count;
            while (i--) { 
   
                this._layout_direction_side(children[i], direction, i);
            }
        },

        layout_offset: function () { 
   
            var node = this.jm.mind.root;
            var layout_data = node._data.layout;
            layout_data.offset_x = 0;
            layout_data.offset_y = 0;
            layout_data.outer_height = 0;
            var children = node.children;
            var i = children.length;
            var left_nodes = [];
            var right_nodes = [];
            var subnode = null;
            while (i--) { 
   
                subnode = children[i];
                if (subnode._data.layout.direction == jm.direction.right) { 
   
                    right_nodes.unshift(subnode);
                } else { 
   
                    left_nodes.unshift(subnode);
                }
            }
            layout_data.left_nodes = left_nodes;
            layout_data.right_nodes = right_nodes;
            layout_data.outer_height_left = this._layout_offset_subnodes(left_nodes);
            layout_data.outer_height_right = this._layout_offset_subnodes(right_nodes);
            this.bounds.e = node._data.view.width / 2;
            this.bounds.w = 0 - this.bounds.e;
            //logger.debug(this.bounds.w);
            this.bounds.n = 0;
            this.bounds.s = Math.max(layout_data.outer_height_left, layout_data.outer_height_right);
        },

        // layout both the x and y axis
        _layout_offset_subnodes: function (nodes) { 
   
            var total_height = 0;
            var nodes_count = nodes.length;
            var i = nodes_count;
            var node = null;
            var node_outer_height = 0;
            var layout_data = null;
            var base_y = 0;
            var pd = null; // parent._data
            while (i--) { 
   
                node = nodes[i];
                layout_data = node._data.layout;
                if (pd == null) { 
   
                    pd = node.parent._data;
                }

                node_outer_height = this._layout_offset_subnodes(node.children);
                if (!node.expanded) { 
   
                    node_outer_height = 0;
                    this.set_visible(node.children, false);
                }
                node_outer_height = Math.max(node._data.view.height, node_outer_height);

                layout_data.outer_height = node_outer_height;
                layout_data.offset_y = base_y - node_outer_height / 2;
                layout_data.offset_x = this.opts.hspace * layout_data.direction + pd.view.width * (pd.layout.direction + layout_data.direction) / 2;
                if (!node.parent.isroot) { 
   
                    layout_data.offset_x += this.opts.pspace * layout_data.direction;
                }

                base_y = base_y - node_outer_height - this.opts.vspace;
                total_height += node_outer_height;
            }
            if (nodes_count > 1) { 
   
                total_height += this.opts.vspace * (nodes_count - 1);
            }
            i = nodes_count;
            var middle_height = total_height / 2;
            while (i--) { 
   
                node = nodes[i];
                node._data.layout.offset_y += middle_height;
            }
            return total_height;
        },

        // layout the y axis only, for collapse/expand a node
        _layout_offset_subnodes_height: function (nodes) { 
   
            var total_height = 0;
            var nodes_count = nodes.length;
            var i = nodes_count;
            var node = null;
            var node_outer_height = 0;
            var layout_data = null;
            var base_y = 0;
            var pd = null; // parent._data
            while (i--) { 
   
                node = nodes[i];
                layout_data = node._data.layout;
                if (pd == null) { 
   
                    pd = node.parent._data;
                }

                node_outer_height = this._layout_offset_subnodes_height(node.children);
                if (!node.expanded) { 
   
                    node_outer_height = 0;
                }
                node_outer_height = Math.max(node._data.view.height, node_outer_height);

                layout_data.outer_height = node_outer_height;
                layout_data.offset_y = base_y - node_outer_height / 2;
                base_y = base_y - node_outer_height - this.opts.vspace;
                total_height += node_outer_height;
            }
            if (nodes_count > 1) { 
   
                total_height += this.opts.vspace * (nodes_count - 1);
            }
            i = nodes_count;
            var middle_height = total_height / 2;
            while (i--) { 
   
                node = nodes[i];
                node._data.layout.offset_y += middle_height;
                //logger.debug(node.topic);
                //logger.debug(node._data.layout.offset_y);
            }
            return total_height;
        },

        get_node_offset: function (node) { 
   
            var layout_data = node._data.layout;
            var offset_cache = null;
            if (('_offset_' in layout_data) && this.cache_valid) { 
   
                offset_cache = layout_data._offset_;
            } else { 
   
                offset_cache = { 
   x: -1, y: -1};
                layout_data._offset_ = offset_cache;
            }
            if (offset_cache.x == -1 || offset_cache.y == -1) { 
   
                var x = layout_data.offset_x;
                var y = layout_data.offset_y;
                if (!node.isroot) { 
   
                    var offset_p = this.get_node_offset(node.parent);
                    x += offset_p.x;
                    y += offset_p.y;
                }
                offset_cache.x = x;
                offset_cache.y = y;
            }
            return offset_cache;
        },

        get_node_point: function (node) { 
   
            var view_data = node._data.view;
            var offset_p = this.get_node_offset(node);
            //logger.debug(offset_p);
            var p = { 
   };
            p.x = offset_p.x + view_data.width * (node._data.layout.direction - 1) / 2;
            p.y = offset_p.y - view_data.height / 2;
            //logger.debug(p);
            return p;
        },

        get_node_point_in: function (node) { 
   
            var p = this.get_node_offset(node);
            return p;
        },

        get_node_point_out: function (node) { 
   
            var layout_data = node._data.layout;
            var pout_cache = null;
            if (('_pout_' in layout_data) && this.cache_valid) { 
   
                pout_cache = layout_data._pout_;
            } else { 
   
                pout_cache = { 
   x: -1, y: -1};
                layout_data._pout_ = pout_cache;
            }
            if (pout_cache.x == -1 || pout_cache.y == -1) { 
   
                if (node.isroot) { 
   
                    pout_cache.x = 0;
                    pout_cache.y = 0;
                } else { 
   
                    var view_data = node._data.view;
                    var offset_p = this.get_node_offset(node);
                    pout_cache.x = offset_p.x + (view_data.width + this.opts.pspace) * node._data.layout.direction;
                    pout_cache.y = offset_p.y;
                    //logger.debug('pout');
                    //logger.debug(pout_cache);
                }
            }
            return pout_cache;
        },

        get_expander_point: function (node) { 
   
            var p = this.get_node_point_out(node);
            var ex_p = { 
   };
            if (node._data.layout.direction == jm.direction.right) { 
   
                ex_p.x = p.x - this.opts.pspace;
            } else { 
   
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace / 2);
            return ex_p;
        },

        get_min_size: function () { 
   
            var nodes = this.jm.mind.nodes;
            var node = null;
            var pout = null;
            for (var nodeid in nodes) { 
   
                node = nodes[nodeid];
                pout = this.get_node_point_out(node);
                //logger.debug(pout.x);
                if (pout.x > this.bounds.e) { 
   
                    this.bounds.e = pout.x;
                }
                if (pout.x < this.bounds.w) { 
   
                    this.bounds.w = pout.x;
                }
            }
            return { 
   
                w: this.bounds.e - this.bounds.w,
                h: this.bounds.s - this.bounds.n
            }
        },

        toggle_node: function (node) { 
   
            if (node.isroot) { 
   
                return;
            }
            if (node.expanded) { 
   
                this.collapse_node(node);
            } else { 
   
                this.expand_node(node);
            }
        },

        expand_node: function (node) { 
   
            node.expanded = true;
            this.part_layout(node);
            this.set_visible(node.children, true);
        },

        collapse_node: function (node) { 
   
            node.expanded = false;
            this.part_layout(node);
            this.set_visible(node.children, false);
        },

        expand_all: function () { 
   
            var nodes = this.jm.mind.nodes;
            var c = 0;
            var node;
            for (var nodeid in nodes) { 
   
                node = nodes[nodeid];
                if (!node.expanded) { 
   
                    node.expanded = true;
                    c++;
                }
            }
            if (c > 0) { 
   
                var root = this.jm.mind.root;
                this.part_layout(root);
                this.set_visible(root.children, true);
            }
        },

        collapse_all: function () { 
   
            var nodes = this.jm.mind.nodes;
            var c = 0;
            var node;
            for (var nodeid in nodes) { 
   
                node = nodes[nodeid];
                if (node.expanded && !node.isroot) { 
   
                    node.expanded = false
                    c++;
                }
            }
            if (c > 0) { 
   
                var root = this.jm.mind.root;
                this.part_layout(root);
                this.set_visible(root.children, true);
            }
        },

        expand_to_depth: function (target_depth, curr_nodes, curr_depth) { 
   
            if (target_depth < 1) { 
   
                return;
            }
            var nodes = curr_nodes || this.jm.mind.root.children;
            var depth = curr_depth || 1;
            var i = nodes.length;
            var node = null;
            while (i--) { 
   
                node = nodes[i];
                if (depth < target_depth) { 
   
                    if (!node.expanded) { 
   
                        this.expand_node(node);
                    }
                    this.expand_to_depth(target_depth, node.children, depth + 1);
                }
                if (depth == target_depth) { 
   
                    if (node.expanded) { 
   
                        this.collapse_node(node);
                    }
                }
            }
        },

        part_layout: function (node) { 
   
            var root = this.jm.mind.root;
            if (!!root) { 
   
                var root_layout_data = root._data.layout;
                if (node.isroot) { 
   
                    root_layout_data.outer_height_right = this._layout_offset_subnodes_height(root_layout_data.right_nodes);
                    root_layout_data.outer_height_left = this._layout_offset_subnodes_height(root_layout_data.left_nodes);
                } else { 
   
                    if (node._data.layout.direction == jm.direction.right) { 
   
                        root_layout_data.outer_height_right = this._layout_offset_subnodes_height(root_layout_data.right_nodes);
                    } else { 
   
                        root_layout_data.outer_height_left = this._layout_offset_subnodes_height(root_layout_data.left_nodes);
                    }
                }
                this.bounds.s = Math.max(root_layout_data.outer_height_left, root_layout_data.outer_height_right);
                this.cache_valid = false;
            } else { 
   
                logger.warn('can not found root node');
            }
        },

        set_visible: function (nodes, visible) { 
   
            var i = nodes.length;
            var node = null;
            var layout_data = null;
            while (i--) { 
   
                node = nodes[i];
                layout_data = node._data.layout;
                if (node.expanded) { 
   
                    this.set_visible(node.children, visible);
                } else { 
   
                    this.set_visible(node.children, false);
                }
                if (!node.isroot) { 
   
                    node._data.layout.visible = visible;
                }
            }
        },

        is_expand: function (node) { 
   
            return node.expanded;
        },

        is_visible: function (node) { 
   
            var layout_data = node._data.layout;
            if (('visible' in layout_data) && !layout_data.visible) { 
   
                return false;
            } else { 
   
                return true;
            }
        },
    };

    // view provider
    jm.view_provider = function (jm, options) { 
   
        this.opts = options;
        this.jm = jm;
        this.layout = jm.layout;

        this.container = null;
        this.e_panel = null;
        this.e_nodes = null;
        this.e_canvas = null;

        this.canvas_ctx = null;
        this.size = { 
   w: 0, h: 0};

        this.selected_node = null;
        this.editing_node = null;
    };

    jm.view_provider.prototype = { 
   
        init: function () { 
   
            logger.debug('view.init');

            this.container = $i(this.opts.container) ? this.opts.container : $g(this.opts.container);
            if (!this.container) { 
   
                logger.error('the options.view.container was not be found in dom');
                return;
            }
            this.e_panel = $c('div');
            this.e_canvas = $c('canvas');
            this.e_nodes = $c('jmnodes');
            this.e_editor = $c('input');

            this.e_panel.className = 'jsmind-inner';
            this.e_panel.appendChild(this.e_canvas);
            this.e_panel.appendChild(this.e_nodes);

            this.e_editor.className = 'jsmind-editor';
            this.e_editor.type = 'text';

            this.actualZoom = 1;
            this.zoomStep = 0.1;
            this.minZoom = 0.5;
            this.maxZoom = 2;

            var v = this;
            jm.util.dom.add_event(this.e_editor, 'keydown', function (e) { 
   
                var evt = e || event;
                if (evt.keyCode == 13) { 
   
                    v.edit_node_end();
                    evt.stopPropagation();
                }
            });
            jm.util.dom.add_event(this.e_editor, 'blur', function (e) { 
   
                v.edit_node_end();
            });

            this.container.appendChild(this.e_panel);

            this.init_canvas();
        },

        add_event: function (obj, event_name, event_handle) { 
   
            jm.util.dom.add_event(this.e_nodes, event_name, function (e) { 
   
                var evt = e || event;
                event_handle.call(obj, evt);
            });
        },

        get_binded_nodeid: function (element) { 
   
            if (element == null) { 
   
                return null;
            }
            var tagName = element.tagName.toLowerCase();
            if (tagName == 'jmnodes' || tagName == 'body' || tagName == 'html') { 
   
                return null;
            }
            if (tagName == 'jmnode' || tagName == 'jmexpander') { 
   
                return element.getAttribute('nodeid');
            } else { 
   
                return this.get_binded_nodeid(element.parentElement);
            }
        },

        is_expander: function (element) { 
   
            return (element.tagName.toLowerCase() == 'jmexpander');
        },

        reset: function () { 
   
            logger.debug('view.reset');
            this.selected_node = null;
            this.clear_lines();
            this.clear_nodes();
            this.reset_theme();
        },

        reset_theme: function () { 
   
            var theme_name = this.jm.options.theme;
            if (!!theme_name) { 
   
                this.e_nodes.className = 'theme-' + theme_name;
            } else { 
   
                this.e_nodes.className = '';
            }
        },

        reset_custom_style: function () { 
   
            var nodes = this.jm.mind.nodes;
            for (var nodeid in nodes) { 
   
                this.reset_node_custom_style(nodes[nodeid]);
            }
        },

        load: function () { 
   
            logger.debug('view.load');
            this.init_nodes();
        },

        expand_size: function () { 
   
            var min_size = this.layout.get_min_size();
            var min_width = min_size.w + this.opts.hmargin * 2;
            var min_height = min_size.h + this.opts.vmargin * 2;
            var client_w = this.e_panel.clientWidth;
            var client_h = this.e_panel.clientHeight;
            if (client_w < min_width) { 
   
                client_w = min_width;
            }
            if (client_h < min_height) { 
   
                client_h = min_height;
            }
            this.size.w = client_w;
            this.size.h = client_h;
        },

        init_canvas: function () { 
   
            var ctx = this.e_canvas.getContext('2d');
            this.canvas_ctx = ctx;
        },

        init_nodes_size: function (node) { 
   
            var view_data = node._data.view;
            view_data.width = view_data.element.clientWidth;
            view_data.height = view_data.element.clientHeight;
        },

        init_nodes: function () { 
   
            var nodes = this.jm.mind.nodes;
            var doc_frag = $d.createDocumentFragment();
            for (var nodeid in nodes) { 
   
                this.create_node_element(nodes[nodeid], doc_frag);
            }
            this.e_nodes.appendChild(doc_frag);
            for (var nodeid in nodes) { 
   
                this.init_nodes_size(nodes[nodeid]);
            }
        },

        add_node: function (node) { 
   
            this.create_node_element(node, this.e_nodes);
            this.init_nodes_size(node);
        },

        create_node_element: function (node, parent_node) { 
   
            var view_data = null;
            if ('view' in node._data) { 
   
                view_data = node._data.view;
            } else { 
   
                view_data = { 
   };
                node._data.view = view_data;
            }

            var d = $c('jmnode');
            if (node.isroot) { 
   
                d.className = 'root';
            } else { 
   
                var d_e = $c('jmexpander');
                $t(d_e, '');
                d_e.setAttribute('nodeid', node.id);
                d_e.style.visibility = 'hidden';
                parent_node.appendChild(d_e);
                view_data.expander = d_e;
            }
            if (!!node.topic) { 
   
                if (this.opts.support_html) { 
   
                    $h(d, node.topic);
                } else { 
   
                    $t(d, node.topic);
                }
            }
            d.setAttribute('nodeid', node.id);
            d.style.visibility = 'hidden';
            this._reset_node_custom_style(d, node.data);

            parent_node.appendChild(d);
            view_data.element = d;
        },

        remove_node: function (node) { 
   
            if (this.selected_node != null && this.selected_node.id == node.id) { 
   
                this.selected_node = null;
            }
            if (this.editing_node != null && this.editing_node.id == node.id) { 
   
                node._data.view.element.removeChild(this.e_editor);
                this.editing_node = null;
            }
            var children = node.children;
            var i = children.length;
            while (i--) { 
   
                this.remove_node(children[i]);
            }
            if (node._data.view) { 
   
                var element = node._data.view.element;
                var expander = node._data.view.expander;
                this.e_nodes.removeChild(element);
                this.e_nodes.removeChild(expander);
                node._data.view.element = null;
                node._data.view.expander = null;
            }
        },

        update_node: function (node) { 
   
            var view_data = node._data.view;
            var element = view_data.element;
            if (!!node.topic) { 
   
                if (this.opts.support_html) { 
   
                    $h(element, node.topic);
                } else { 
   
                    $t(element, node.topic);
                }
            }
            view_data.width = element.clientWidth;
            view_data.height = element.clientHeight;
        },

        select_node: function (node) { 
   
            if (!!this.selected_node) { 
   
                this.selected_node._data.view.element.className =
                    this.selected_node._data.view.element.className.replace(/\s*selected\b/i, '');
                this.reset_node_custom_style(this.selected_node);
            }
            if (!!node) { 
   
                this.selected_node = node;
                node._data.view.element.className += ' selected';
                this.clear_node_custom_style(node);
            }
        },

        select_clear: function () { 
   
            this.select_node(null);
        },

        get_editing_node: function () { 
   
            return this.editing_node;
        },

        is_editing: function () { 
   
            return (!!this.editing_node);
        },

        edit_node_begin: function (node) { 
   
            if (!node.topic) { 
   
                logger.warn("don't edit image nodes");
                return;
            }
            if (this.editing_node != null) { 
   
                this.edit_node_end();
            }
            this.editing_node = node;
            var view_data = node._data.view;
            var element = view_data.element;
            var topic = node.topic;
            var ncs = getComputedStyle(element);
            this.e_editor.value = topic;
            this.e_editor.style.width = (element.clientWidth - parseInt(ncs.getPropertyValue('padding-left')) - parseInt(ncs.getPropertyValue('padding-right'))) + 'px';
            element.innerHTML = '';
            element.appendChild(this.e_editor);
            element.style.zIndex = 5;
            this.e_editor.focus();
            this.e_editor.select();
        },

        edit_node_end: function () { 
   
            if (this.editing_node != null) { 
   
                var node = this.editing_node;
                this.editing_node = null;
                var view_data = node._data.view;
                var element = view_data.element;
                var topic = this.e_editor.value;
                element.style.zIndex = 'auto';
                element.removeChild(this.e_editor);
                if (jm.util.text.is_empty(topic) || node.topic === topic) { 
   
                    if (this.opts.support_html) { 
   
                        $h(element, node.topic);
                    } else { 
   
                        $t(element, node.topic);
                    }
                } else { 
   
                    this.jm.update_node(node.id, topic);
                }
            }
        },

        get_view_offset: function () { 
   
            var bounds = this.layout.bounds;
            var _x = (this.size.w - bounds.e - bounds.w) / 2;
            var _y = this.size.h / 2;
            return { 
   x: _x, y: _y};
        },

        resize: function () { 
   
            this.e_canvas.width = 1;
            this.e_canvas.height = 1;
            this.e_nodes.style.width = '1px';
            this.e_nodes.style.height = '1px';

            this.expand_size();
            this._show();
        },

        _show: function () { 
   
            this.e_canvas.width = this.size.w;
            this.e_canvas.height = this.size.h;
            this.e_nodes.style.width = this.size.w + 'px';
            this.e_nodes.style.height = this.size.h + 'px';
            this.show_nodes();
            this.show_lines();
            //this.layout.cache_valid = true;
            this.jm.invoke_event_handle(jm.event_type.resize, { 
   data: []});
        },

        zoomIn: function () { 
   
            return this.setZoom(this.actualZoom + this.zoomStep);
        },

        zoomOut: function () { 
   
            return this.setZoom(this.actualZoom - this.zoomStep);
        },

        setZoom: function (zoom) { 
   
            if ((zoom < this.minZoom) || (zoom > this.maxZoom)) { 
   
                return false;
            }
            this.actualZoom = zoom;
            for (var i = 0; i < this.e_panel.children.length; i++) { 
   
                this.e_panel.children[i].style.transform = 'scale(' + zoom + ')';
            }
            ;
            this.show(true);
            return true;

        },

        _center_root: function () { 
   
            // center root node
            var outer_w = this.e_panel.clientWidth;
            var outer_h = this.e_panel.clientHeight;
            if (this.size.w > outer_w) { 
   
                var _offset = this.get_view_offset();
                this.e_panel.scrollLeft = _offset.x - outer_w / 2;
            }
            if (this.size.h > outer_h) { 
   
                this.e_panel.scrollTop = (this.size.h - outer_h) / 2;
            }
        },

        show: function (keep_center) { 
   
            logger.debug('view.show');
            this.expand_size();
            this._show();
            if (!!keep_center) { 
   
                this._center_root();
            }
        },

        relayout: function () { 
   
            this.expand_size();
            this._show();
        },

        save_location: function (node) { 
   
            var vd = node._data.view;
            vd._saved_location = { 
   
                x: parseInt(vd.element.style.left) - this.e_panel.scrollLeft,
                y: parseInt(vd.element.style.top) - this.e_panel.scrollTop,
            };
        },

        restore_location: function (node) { 
   
            var vd = node._data.view;
            this.e_panel.scrollLeft = parseInt(vd.element.style.left) - vd._saved_location.x;
            this.e_panel.scrollTop = parseInt(vd.element.style.top) - vd._saved_location.y;
        },

        clear_nodes: function () { 
   
            var mind = this.jm.mind;
            if (mind == null) { 
   
                return;
            }
            var nodes = mind.nodes;
            var node = null;
            for (var nodeid in nodes) { 
   
                node = nodes[nodeid];
                node._data.view.element = null;
                node._data.view.expander = null;
            }
            this.e_nodes.innerHTML = '';
        },

        show_nodes: function () { 
   
            var nodes = this.jm.mind.nodes;
            var node = null;
            var node_element = null;
            var expander = null;
            var p = null;
            var p_expander = null;
            //关闭子节点按钮
            var expander_text = '';
            var view_data = null;
            var _offset = this.get_view_offset();
            for (var nodeid in nodes) { 
   
                node = nodes[nodeid];
                view_data = node._data.view;
                node_element = view_data.element;
                expander = view_data.expander;
                if (!this.layout.is_visible(node)) { 
   
                    node_element.style.display = 'none';
                    expander.style.display = 'none';
                    continue;
                }
                this.reset_node_custom_style(node);
                p = this.layout.get_node_point(node);
                view_data.abs_x = _offset.x + p.x;
                view_data.abs_y = _offset.y + p.y;
                node_element.style.left = (_offset.x + p.x) + 'px';
                node_element.style.top = (_offset.y + p.y) + 'px';
                node_element.style.display = '';
                node_element.style.visibility = 'visible';
                if (!node.isroot && node.children.length > 0) { 
   
                    //关闭子节点按钮
                    expander_text = node.expanded ? '㊀' : ' ㊉';
                    p_expander = this.layout.get_expander_point(node);
                    expander.style.left = (_offset.x + p_expander.x) + 'px';
                    expander.style.top = (_offset.y + p_expander.y) + 'px';
                    expander.style.display = '';
                    expander.style.visibility = 'visible';
                    $t(expander, expander_text);
                }
                // hide expander while all children have been removed
                if (!node.isroot && node.children.length == 0) { 
   
                    expander.style.display = 'none';
                    expander.style.visibility = 'hidden';
                }
            }
        },

        reset_node_custom_style: function (node) { 
   
            this._reset_node_custom_style(node._data.view.element, node.data);
        },

        _reset_node_custom_style: function (node_element, node_data) { 
   
            if ('background-color' in node_data) { 
   
                node_element.style.backgroundColor = node_data['background-color'];
            }
            if ('foreground-color' in node_data) { 
   
                node_element.style.color = node_data['foreground-color'];
            }
            if ('width' in node_data) { 
   
                node_element.style.width = node_data['width'] + 'px';
            }
            if ('height' in node_data) { 
   
                node_element.style.height = node_data['height'] + 'px';
            }
            if ('font-size' in node_data) { 
   
                node_element.style.fontSize = node_data['font-size'] + 'px';
            }
            if ('font-weight' in node_data) { 
   
                node_element.style.fontWeight = node_data['font-weight'];
            }
            if ('font-style' in node_data) { 
   
                node_element.style.fontStyle = node_data['font-style'];
            }
            if ('background-image' in node_data) { 
   
                var backgroundImage = node_data['background-image'];
                if (backgroundImage.startsWith('data') && node_data['width'] && node_data['height']) { 
   
                    var img = new Image();

                    img.onload = function () { 
   
                        var c = $c('canvas');
                        c.width = node_element.clientWidth;
                        c.height = node_element.clientHeight;
                        var img = this;
                        if (c.getContext) { 
   
                            var ctx = c.getContext('2d');
                            ctx.drawImage(img, 2, 2, node_element.clientWidth, node_element.clientHeight);
                            var scaledImageData = c.toDataURL();
                            node_element.style.backgroundImage = 'url(' + scaledImageData + ')';
                        }
                    };
                    img.src = backgroundImage;

                } else { 
   
                    node_element.style.backgroundImage = 'url(' + backgroundImage + ')';
                }
                node_element.style.backgroundSize = '99%';

                if ('background-rotation' in node_data) { 
   
                    node_element.style.transform = 'rotate(' + node_data['background-rotation'] + 'deg)';
                }

            }
        },

        clear_node_custom_style: function (node) { 
   
            var node_element = node._data.view.element;
            node_element.style.backgroundColor = "";
            node_element.style.color = "";
        },

        clear_lines: function (canvas_ctx) { 
   
            var ctx = canvas_ctx || this.canvas_ctx;
            jm.util.canvas.clear(ctx, 0, 0, this.size.w, this.size.h);
        },

        show_lines: function (canvas_ctx) { 
   
            this.clear_lines(canvas_ctx);
            var nodes = this.jm.mind.nodes;
            var node = null;
            var pin = null;
            var pout = null;
            var _offset = this.get_view_offset();
            for (var nodeid in nodes) { 
   
                node = nodes[nodeid];
                if (!!node.isroot) { 
   
                    continue;
                }
                if (('visible' in node._data.layout) && !node._data.layout.visible) { 
   
                    continue;
                }
                pin = this.layout.get_node_point_in(node);
                pout = this.layout.get_node_point_out(node.parent);
                this.draw_line(pout, pin, _offset, canvas_ctx);
            }
        },

        //绘制连接线
        draw_line: function (pin, pout, offset, canvas_ctx) { 
   
            var ctx = canvas_ctx || this.canvas_ctx;
            ctx.strokeStyle = this.opts.line_color;
            ctx.lineWidth = this.opts.line_width;
            ctx.lineCap = 'round';

            //画折线
            ctx.moveTo(pin.x + offset.x, pin.y + offset.y);       //设置起点状态
            ctx.lineTo(pin.x + offset.x, pout.y + offset.y);       //设置末端状态
            ctx.moveTo(pin.x + offset.x, pout.y + offset.y);       //设置起点状态
            ctx.lineTo(pout.x + offset.x, pout.y + offset.y);       //设置末端状态
            ctx.lineWidth = 2;          //设置线宽状态
            ctx.stroke();               //进行绘制

            //画贝塞尔曲线
            // jm.util.canvas.bezierto(
            // ctx,
            // pin.x + offset.x,
            // pin.y + offset.y,
            // pout.x + offset.x,
            // pout.y + offset.y);
        },
    };

    // shortcut provider
    jm.shortcut_provider = function (jm, options) { 
   
        this.jm = jm;
        this.opts = options;
        this.mapping = options.mapping;
        this.handles = options.handles;
        this._mapping = { 
   };
    };

    jm.shortcut_provider.prototype = { 
   
        init: function () { 
   
            jm.util.dom.add_event($d, 'keydown', this.handler.bind(this));

            this.handles['addchild'] = this.handle_addchild;
            this.handles['addbrother'] = this.handle_addbrother;
            this.handles['editnode'] = this.handle_editnode;
            this.handles['delnode'] = this.handle_delnode;
            this.handles['toggle'] = this.handle_toggle;
            this.handles['up'] = this.handle_up;
            this.handles['down'] = this.handle_down;
            this.handles['left'] = this.handle_left;
            this.handles['right'] = this.handle_right;

            for (var handle in this.mapping) { 
   
                if (!!this.mapping[handle] && (handle in this.handles)) { 
   
                    this._mapping[this.mapping[handle]] = this.handles[handle];
                }
            }
        },

        enable_shortcut: function () { 
   
            this.opts.enable = true;
        },

        disable_shortcut: function () { 
   
            this.opts.enable = false;
        },

        handler: function (e) { 
   
            if (this.jm.view.is_editing()) { 
   
                return;
            }
            var evt = e || event;
            if (!this.opts.enable) { 
   
                return true;
            }
            var kc = evt.keyCode;
            if (kc in this._mapping) { 
   
                this._mapping[kc].call(this, this.jm, e);
            }
        },

        handle_addchild: function (_jm, e) { 
   
            var selected_node = _jm.get_selected_node();
            if (!!selected_node) { 
   
                var nodeid = jm.util.uuid.newid();
                var node = _jm.add_node(selected_node, nodeid, 'New Node');
                if (!!node) { 
   
                    _jm.select_node(nodeid);
                    _jm.begin_edit(nodeid);
                }
            }
        },
        handle_addbrother: function (_jm, e) { 
   
            var selected_node = _jm.get_selected_node();
            if (!!selected_node && !selected_node.isroot) { 
   
                var nodeid = jm.util.uuid.newid();
                var node = _jm.insert_node_after(selected_node, nodeid, 'New Node');
                if (!!node) { 
   
                    _jm.select_node(nodeid);
                    _jm.begin_edit(nodeid);
                }
            }
        },
        handle_editnode: function (_jm, e) { 
   
            var selected_node = _jm.get_selected_node();
            if (!!selected_node) { 
   
                _jm.begin_edit(selected_node);
            }
        },
        handle_delnode: function (_jm, e) { 
   
            var selected_node = _jm.get_selected_node();
            if (!!selected_node && !selected_node.isroot) { 
   
                _jm.select_node(selected_node.parent);
                _jm.remove_node(selected_node);
            }
        },
        handle_toggle: function (_jm, e) { 
   
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            if (!!selected_node) { 
   
                _jm.toggle_node(selected_node.id);
                evt.stopPropagation();
                evt.preventDefault();
            }
        },
        handle_up: function (_jm, e) { 
   
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            if (!!selected_node) { 
   
                var up_node = _jm.find_node_before(selected_node);
                if (!up_node) { 
   
                    var np = _jm.find_node_before(selected_node.parent);
                    if (!!np && np.children.length > 0) { 
   
                        up_node = np.children[np.children.length - 1];
                    }
                }
                if (!!up_node) { 
   
                    _jm.select_node(up_node);
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        },

        handle_down: function (_jm, e) { 
   
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            if (!!selected_node) { 
   
                var down_node = _jm.find_node_after(selected_node);
                if (!down_node) { 
   
                    var np = _jm.find_node_after(selected_node.parent);
                    if (!!np && np.children.length > 0) { 
   
                        down_node = np.children[0];
                    }
                }
                if (!!down_node) { 
   
                    _jm.select_node(down_node);
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        },

        handle_left: function (_jm, e) { 
   
            this._handle_direction(_jm, e, jm.direction.left);
        },
        handle_right: function (_jm, e) { 
   
            this._handle_direction(_jm, e, jm.direction.right);
        },
        _handle_direction: function (_jm, e, d) { 
   
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            var node = null;
            if (!!selected_node) { 
   
                if (selected_node.isroot) { 
   
                    var c = selected_node.children;
                    var children = [];
                    for (var i = 0; i < c.length; i++) { 
   
                        if (c[i].direction === d) { 
   
                            children.push(i)
                        }
                    }
                    node = c[children[Math.floor((children.length - 1) / 2)]];
                } else if (selected_node.direction === d) { 
   
                    var children = selected_node.children;
                    var childrencount = children.length;
                    if (childrencount > 0) { 
   
                        node = children[Math.floor((childrencount - 1) / 2)]
                    }
                } else { 
   
                    node = selected_node.parent;
                }
                if (!!node) { 
   
                    _jm.select_node(node);
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        },
    };


    // plugin
    jm.plugin = function (name, init) { 
   
        this.name = name;
        this.init = init;
    };

    jm.plugins = [];

    jm.register_plugin = function (plugin) { 
   
        if (plugin instanceof jm.plugin) { 
   
            jm.plugins.push(plugin);
        }
    };

    jm.init_plugins = function (sender) { 
   
        $w.setTimeout(function () { 
   
            jm._init_plugins(sender);
        }, 0);
    };

    jm._init_plugins = function (sender) { 
   
        var l = jm.plugins.length;
        var fn_init = null;
        for (var i = 0; i < l; i++) { 
   
            fn_init = jm.plugins[i].init;
            if (typeof fn_init === 'function') { 
   
                fn_init(sender);
            }
        }
    };

    // quick way
    jm.show = function (options, mind) { 
   
        var _jm = new jm(options);
        _jm.show(mind);
        return _jm;
    };

    // export jsmind
    if (typeof module !== 'undefined' && typeof exports === 'object') { 
   
        module.exports = jm;
    } else if (typeof define === 'function' && (define.amd || define.cmd)) { 
   
        define(function () { 
   
            return jm;
        });
    } else { 
   
        $w[__name__] = jm;
    }
})(typeof window !== 'undefined' ? window : global);


5.mind.js


import { 
    ElMessage } from "element-plus";
let name = ''
const handleClick = function (checked, name,id,topic_en) { 
   
  console.log('###############勾选结点',checked, name,id,topic_en); 
   window.name = name
  if(checked===true){ 
   
    window.checkedList.add(topic_en)
  }else{ 
   
    window.checkedList.delete(topic_en)
  }   
    // window.checkedList.delete(currentPath.split(','))
  
// getPathById(window.uncheckedData, topic_en, res => { 
   
// let path = res.join(',');
// window.targetData.push( path.split(','));
// });
  console.log('###############目标结点',window.targetData  ); 
  console.log('############window.checkedList',window.checkedList); 
};
// const getNodePath=()=>{ 
   
// let temp=Array.from( window.checkedList);
// temp.forEach((item)=>{ 
   
// getPathById(window.uncheckedData, item, res => { 
   
     
// let path = res.join(',');
// window.targetData.push( path.split(','));
     
// // let currentPath= res.join(',');
     
// // console.log('###############目标结点',currentPath)
// // window.checkedList.add( currentPath.split(','))
     
// });
// })
// }
// //根据topic_en,计算树结构路径点
// const getPathById=(catalog, topic_en, callback)=>{ 
   
// //定义变量保存当前结果路径
// let nodepath=[];
// try { 
   
// function getNodePath(node) { 
   
// // temppath.push(node.id);
// nodepath.push(node.topic_en);
// //找到符合条件的节点,通过throw终止掉递归
// if (node.topic_en === topic_en) { 
   
// throw ('');
// }
// if (node.children && node.children.length > 0) { 
   
// for (let i = 0; i < node.children.length; i++) { 
   
// getNodePath(node.children[i]);
// }
// //当前节点的子节点遍历完依旧没找到,则删除路径中的该节点
// nodepath.pop();

// } else { 
   
// //找到叶子节点时,删除路径当中的该叶子节点
// nodepath.pop();
// }
// }
// getNodePath(catalog);
// } catch (e) { 
   
// let result =nodepath;
// callback(result);
// }
// }
const handleInput=function(value, name,id){ 
   
  console.log('编辑权重',value, name,id);
  window.name = name
  console.log("metaData",window.metaData);
  if(!(/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/).test(value)){ 
   
    // this.$message({ 
   
    // type: "error",
    // message: "权重值不能为负数!",
    // duration: 2000,
    // });
    ElMessage({ 
   
      message: "权重值不能为负数!",
      type: "warning",
    });
    console.log('负数');
    return;
  }
  DFS(window.metaData).forEach((item)=>{ 
   
     if(item.id===id ){ 
   
        item.number=[value]
     }
  })
  console.log('修改后数据',window.metaData);
}
const DFS = (node, nodeList = []) => { 
   
	//node不能为null
    if (node !== null) { 
   
        nodeList.push(node)
        let children = node.children || []
        //如果children.length存在
        for (let i = 0; i < children.length; i++) { 
   
        	//递归调用
            DFS(children[i], nodeList)
        }
    }
    // console.log('nodeList',nodeList);
    return nodeList
}  
  

6. test.vue

<template>
  <div class="test">
    <minder :data="mindData" :type="type"></minder>
  </div>
</template>

<script lang="ts"> import { 
      defineComponent, reactive, toRefs } from "vue"; import Minder from "@/components/minder/DataMind.vue"; export default defineComponent({ 
      name: "test", components: { 
      Minder }, setup() { 
      const state = reactive({ 
      type: "check", //type:defalut,check,label,label2,input mindData: (window as any).mindData, }); const number: number | number[] = state.type === "label2" ? [98, 65, 21, 78] : [13]; //[98标签1,65标签2,方差,均值] const initData = () => { 
      (window as any).mindData = { 
      id: "root", topic: "根节点名字", number: [88], children: [ { 
      id: "easy", topic: "规章制度", children: [ { 
      id: "easy1", topic: "请休假制度" }, { 
      id: "easy2", topic: "考勤制度" }, ], }, { 
      id: "open", topic: "岗位职责", children: [ { 
      id: "open1", topic: "人事部职责", children: [ { 
      id: "open1-1", topic: "行政管理" }, { 
      id: "open1-2", topic: "人事管理" }, ], }, { 
      id: "open2", topic: "信息部职责", children: [ { 
      id: "open2-1", topic: "信息安全" }, { 
      id: "open2-2", topic: "信息排查" }, { 
      id: "open2-3", topic: "信息汇总" }, { 
      id: "open2-4", topic: "ERP" }, ], }, { 
      id: "open3", topic: "生产车间", children: [ { 
      id: "open3-1", topic: "安全制度" }, { 
      id: "open3-2", topic: "车间操作手册" }, ], }, ], }, { 
      id: "powerful", topic: "员工福利", number, children: [ { 
      id: "powerful1", topic: "雪天", tip: "3366" }, { 
      id: "powerful2", topic: "年假", tip: "98999" }, { 
      id: "powerful3", topic: "法定节假日", number, }, { 
      id: "powerful4", topic: "生日祝福" }, { 
      id: "powerful5", topic: "成长与进步", children: [{ 
      id: "powerful5-1", topic: "员工培训" }], }, ], }, ], }; }; setTimeout(() => { 
      initData(); state.mindData = (window as any).mindData; }, 100); return { 
      ...toRefs(state), }; }, mounted() { 
      function generateParenthesis(n: number): string[] { 
     } }, }); </script>
<style lang="scss" scoped> .test { 
      width: 100%; height: 100%; padding: 20px; } </style>

7. 运行效果

在这里插入图片描述

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/26480.html

(1)
上一篇 2023-05-03 15:00
下一篇 2023-05-16 15:00

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信