Gg-editor is a visual operation application framework with secondary encapsulation based on G6-Editor. However, there are too few relevant usage instructions at present, no complete project application case, and the core code gg-Editor-core is not open source. How to develop a brain map application based on GG-Editor

Introduce a,

This project is a brain map application developed based on GG-Editor, which is basically a relatively complete application. It can be said that it has already stepped over the pit for you in advance. In addition, all the code of this project can be realized a flow chart application very quickly, because the basic API usage is the same. In addition, this article will not talk too much about the content of official documents, but will focus on the use.

Second, the use of

Install and use gg-Editor to install dependencies directly through NPM or YARN

npm install --save gg-editor
Copy the code

Let’s get into development quickly. First, go to Github and pull the project to the local github.com/alibaba/GGE… , we will take its own Demo as a template, on this basis for development.

Run the yarn run start command to start the effect drawing

Now let’s transform the project

1. Customize a node

Official documentation: ggeditor.com/docs/api/re…

The components folder new EditorCustomNode/index JSX

import React, { Fragment } from 'react';
import { RegisterNode } from 'gg-editor';

import PLUS_URL from '@/assets/plus.svg';
import MINUS_URL from '@/assets/minus.svg';
import CATE_URL from '@/assets/docs.svg';
import CASE_URL from '@/assets/file.svg';

const ICON_SIZE = 16;
const COLLAPSED_ICON = 'node-inner-icon';

function getRectPath(x: string | number, y: any, w: number, h: number, r: number) {
  if (r) {
    return[['M', +x + +r, y],
      ['l', w - r * 2.0],
      ['a', r, r, 0.0.1, r, r],
      ['l'.0, h - r * 2],
      ['a', r, r, 0.0.1, -r, r],
      ['l', r * 2 - w, 0],
      ['a', r, r, 0.0.1, -r, -r],
      ['l'.0, r * 2 - h],
      ['a', r, r, 0.0.1, r, -r],
      ['z']]; }const res = [['M', x, y], ['l', w, 0], ['l'.0, h], ['l', -w, 0], ['z']];

  res.toString = toString;

  return res;
}

class EditorCustomNode extends React.Component {
  render() {
    const config = {
      // Draw the label
      // drawLabel(item) {
      // },

      // Draw an icon
      afterDraw(item) {
        const model = item.getModel();
        const group = item.getGraphicGroup();

        const label = group.findByClass('label') [0];
        const labelBox = label.getBBox();

        const { width } = labelBox;
        const { height } = labelBox;
        const x = -width / 2;
        const y = -height / 2;

        const { type, collapsed, children } = model;

        // Collapse the status icon
        if (type === 'cate' && children && children.length > 0) {
          group.addShape('image', {
            className: COLLAPSED_ICON,
            attrs: {
              img: collapsed ? PLUS_URL : MINUS_URL,
              x: x - 24.y: y - 2.width: ICON_SIZE,
              height: ICON_SIZE,
              style: 'cursor: pointer',}}); }// File type icon
        group.addShape('image', {
          attrs: {
            img: type === 'cate' ? CATE_URL : CASE_URL,
            x: children && children.length > 0 ? x + 4 : x - 12.y: y - 2.width: ICON_SIZE,
            height: ICON_SIZE,
          },
        });
      },

      // Align labels
      adjustLabelPosition(item, labelShape) {
        const size = this.getSize(item);
        const padding = this.getPadding(item);
        const width = size[0];
        const labelBox = labelShape.getBBox();

        labelShape.attr({
          x: -width / 2 + padding[3].y: -labelBox.height / 2}); },// Built-in margins
      // [up, right, down, left]
      getPadding(item) {
        const model = item.getModel();
        const { children } = model;
        if (children && children.length > 0) {
          return [12.8.12.60];
        }
        return [12.8.12.38];
      },

      // Label size
      // [width, height]
      getSize(item) {
        const group = item.getGraphicGroup();

        const label = group.findByClass('label') [0];
        const labelBox = label.getBBox();

        const padding = this.getPadding(item);

        return [
          labelBox.width + padding[1] + padding[3],
          labelBox.height + padding[0] + padding[2]]; },// Node path
      // x, y, w, h, r
      getPath(item) {
        const size = this.getSize(item);
        const style = this.getStyle(item);

        return getRectPath(-size[0] / 2, -size[1] / 2, size[0] + 4, size[1], style.radius);
      },

      // Node style
      getStyle(item) {
        return {
          // stroke: '#d9d9d9',
          radius: 2.lineWidth: 1}; },// Label style
      getLabelStyle(item) {
        return {
          fill: 'rgba (0,0,0,0.65)'.lineHeight: 18.fontSize: 16}; },// Activate the style
      getActivedStyle(item) {
        return {
          stroke: '#096dd9'}; },// Select the style
      getSelectedStyle(item) {
        return {
          stroke: '#096dd9'}; }};return (
      <Fragment>
        <RegisterNode name="mind-base" config={config} extend="mind-base" />
        <RegisterNode name="custom-node" config={config} extend="mind-base" />
      </Fragment>
    );
  }
}

export default EditorCustomNode;
Copy the code

Modify the Mind folder and add the following code

import EditorCustomNode from '.. /components/EditorCustomNode';

<Mind data={data} className={styles.mind}
// rootShapeCustomize the root node //rootShape="custom-root"
firstSubShape="custom-node"
secondSubShape="custom-node"
/>{/* Custom node */}<EditorCustomNode />
Copy the code

2. Customize shortcut events

Official documentation: ggeditor.com/docs/api/re…

Add EditorCommand/index.jsx to the Components folder

import React from 'react';
import { RegisterCommand } from 'gg-editor';

function addNodeCall(editor, node, id, type) {
  const graph = editor.getGraph();
  const model = node.getModel();
  const sideNode = editor.getFirstChildrenBySide('left');
  const currentNode = sideNode[0] && graph.find(sideNode[0].id);
  return graph.add('node', {
    id,
    parent: node.id,
    label: 'New Node',
    type,
    side: model.children.length > 2 ? 'left' : 'right'.nth: currentNode ? graph.getNth(currentNode) : void 0}); }class CustomCommand extends React.Component {
  componentDidMount() {
    document.addEventListener('keydown', event => {
      event.preventDefault();
    });
  }

  render() {
    return [
      // Enter adds the case of the same level
      <RegisterCommand
        key="customAppendCase"
        name="customAppendCase"
        config={{
          enable(editor) {
            const currentPage = editor.getCurrentPage();
            const selected = currentPage.getSelected();
            return currentPage.isMind && selected.length === 1;
          },
          getItem(editor) {
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            return this.selectedItemId
              ? graph.find(this.selectedItemId)
              : currentPage.getSelected()[0];
          },
          execute(editor) {
            let addNode;
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            const root = currentPage.getRoot();
            const node = this.getItem(editor);
            const model = node.getModel();
            const { hierarchy } = model;
            const parent = node.getParent();
            if (node.isRoot) addNode = addNodeCall(currentPage, node, this.addItemId, 'case');
            else {
              const l = graph.getNth(node);
              addNode = graph.add('node', {
                id: this.addItemId,
                parent: parent.id,
                side: hierarchy === 2 && root.children.length === 3 ? 'left' : model.side,
                label: 'New Node'.type: 'case'.nth: model.side === 'left' && hierarchy === 2 ? l : l + 1}); } currentPage.clearSelected(), currentPage.clearActived(), currentPage.setSelected(addNode,true),
              this.executeTimes === 1 &&
                ((this.selectedItemId = node.id),
                (this.addItemId = addNode.id),
                currentPage.beginEditLabel(addNode));
          },
          back(editor: any) {
            const currentPage = editor.getCurrentPage();
            currentPage.getGraph().remove(this.addItemId),
              currentPage.clearSelected(),
              currentPage.clearActived(),
              currentPage.setSelected(this.selectedItemId, true);
          },
          shortcutCodes: ['Enter'],
        }}
      />,
      // Tab adds the lower case
      <RegisterCommand
        key="customAppendChildCase"
        name="customAppendChildCase"
        config={{
          enable(editor: any) {
            const currentPage = editor.getCurrentPage();
            const selected = currentPage.getSelected();
            const node = this.getItem(editor);
            // Creating secondary cases is not allowed
            const isCase = node && node.getModel().type === 'case';
            return currentPage.isMind && selected.length > 0 && !isCase;
          },
          getItem(editor: any) {
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            return this.selectedItemId
              ? graph.find(this.selectedItemId)
              : currentPage.getSelected()[0];
          },
          execute(editor: any) {
            let addNode;
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            const node = this.getItem(editor);
            (addNode = node.isRoot
              ? addNodeCall(currentPage, node, this.addItemId, 'case')
              : graph.add('node', {
                  id: this.addItemId,
                  parent: node.id,
                  label: 'New Node'.type: 'case',
                })),
              currentPage.clearSelected(),
              currentPage.clearActived(),
              currentPage.setSelected(addNode, true),
              this.executeTimes === 1 &&
                ((this.selectedItemId = node.id),
                (this.addItemId = addNode.id),
                currentPage.beginEditLabel(addNode));
          },
          back(editor: any) {
            const currentPage = editor.getCurrentPage();
            currentPage.getGraph().remove(this.addItemId),
              currentPage.clearSelected(),
              currentPage.clearActived(),
              currentPage.setSelected(this.selectedItemId, true);
          },
          shortcutCodes: ['Tab'],
        }}
      />,
      // ⌘ + N Add sibling cate
      <RegisterCommand
        key="customAppendCate"
        name="customAppendCate"
        config={{
          enable(editor: any) {
            const currentPage = editor.getCurrentPage();
            const selected = currentPage.getSelected();
            return currentPage.isMind && selected.length === 1;
          },
          getItem(editor: any) {
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            return this.selectedItemId
              ? graph.find(this.selectedItemId)
              : currentPage.getSelected()[0];
          },
          execute(editor: any) {
            let addNode;
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            const root = currentPage.getRoot();
            const node = this.getItem(editor);
            const model = node.getModel();
            const { hierarchy } = model;
            const parent = node.getParent();
            if (node.isRoot) addNode = addNodeCall(currentPage, node, this.addItemId, 'cate');
            else {
              const index = graph.getNth(node);
              addNode = graph.add('node', {
                id: this.addItemId,
                parent: parent.id,
                side: hierarchy === 2 && root.children.length === 3 ? 'left' : model.side,
                label: 'New Node'.type: 'cate'.nth: model.side === 'left' && hierarchy === 2 ? index : index + 1}); } currentPage.clearSelected(), currentPage.clearActived(), currentPage.setSelected(addNode,true),
              this.executeTimes === 1 &&
                ((this.selectedItemId = node.id),
                (this.addItemId = addNode.id),
                currentPage.beginEditLabel(addNode));
          },
          back(editor: any) {
            const currentPage = editor.getCurrentPage();
            currentPage.getGraph().remove(this.addItemId),
              currentPage.clearSelected(),
              currentPage.clearActived(),
              currentPage.setSelected(this.selectedItemId, true);
          },
          shortcutCodes: ['metaKey'.'n'],
        }}
      />,
      // ⌘ + ⇧ + N Add subordinate cate
      <RegisterCommand
        key="customAppendChildCate"
        name="customAppendChildCate"
        config={{
          enable(editor: any) {
            const currentPage = editor.getCurrentPage();
            const selected = currentPage.getSelected();
            const node = this.getItem(editor);
            // Sub-cate creation is not allowed
            const isCase = node && node.getModel().type === 'case';
            return currentPage.isMind && selected.length > 0 && !isCase;
          },
          getItem(editor: any) {
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            return this.selectedItemId
              ? graph.find(this.selectedItemId)
              : currentPage.getSelected()[0];
          },
          execute(editor: any) {
            let addNode;
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            const node = this.getItem(editor);
            (addNode = node.isRoot
              ? addNodeCall(currentPage, node, this.addItemId, 'cate')
              : graph.add('node', {
                  id: this.addItemId,
                  parent: node.id,
                  label: 'New Node'.type: 'cate',
                })),
              currentPage.clearSelected(),
              currentPage.clearActived(),
              currentPage.setSelected(addNode, true),
              this.executeTimes === 1 &&
                ((this.selectedItemId = node.id),
                (this.addItemId = addNode.id),
                currentPage.beginEditLabel(addNode));
          },
          back(editor: any) {
            const currentPage = editor.getCurrentPage();
            currentPage.getGraph().remove(this.addItemId),
              currentPage.clearSelected(),
              currentPage.clearActived(),
              currentPage.setSelected(this.selectedItemId, true);
          },
          shortcutCodes: ['metaKey'.'shiftKey'.'N'],
        }}
      />,
      ⌘ + B fold/open
      <RegisterCommand
        key="customCollapseExpand"
        name="customCollapseExpand"
        config={{
          enable(editor) {
            const node = this.getItem(editor);
            if(! node) {return false;
            }
            const { type } = node.getModel();
            const hasChild = node.getChildren().length > 0;
            returnnode && hasChild && type ! = ='case';
          },
          getItem(editor) {
            const currentPage = editor.getCurrentPage();
            const grapg = currentPage.getGraph();
            return this.itemId ? grapg.find(this.itemId) : currentPage.getSelected()[0];
          },
          execute(editor) {
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            const node = this.getItem(editor);
            node.getModel().collapsed
              ? (graph.update(node, {
                  collapsed: false,
                }),
                node.getInnerEdges &&
                  node.getInnerEdges().forEach(editor= >{ editor.update(); }),this.toCollapsed = false))
              : (graph.update(node, {
                  collapsed: true,}),this.toCollapsed = true)),
              currentPage.clearSelected(),
              currentPage.setSelected(node, true),
              this.executeTimes === 1 && (this.itemId = node.id);
          },
          back(editor) {
            const currentPage = editor.getCurrentPage();
            const graph = currentPage.getGraph();
            const node = this.getItem(editor);
            this.toCollapsed
              ? graph.update(node, {
                  collapsed: false,
                })
              : graph.update(node, {
                  collapsed: true,
                }),
              currentPage.clearSelected(),
              currentPage.setSelected(node, true);
          },
          shortcutCodes: [['metaKey'.'b'],
            ['ctrlKey'.'b']],}} / >,⌘ + B to fold
      <RegisterCommand
        key="customCollapse"
        name="customCollapse"
        extend="customCollapseExpand"
        config={{
          enable(editor) {
            const node = this.getItem(editor);
            if(! node) {return false;
            }
            const { type, collapsed } = node.getModel();
            const hasChild = node.getChildren().length > 0;
            returnnode && hasChild && type ! = ='case'&&! collapsed; }}} / >,// ⌘ + B to deploy
      <RegisterCommand
        key="customExpand"
        name="customExpand"
        extend="customCollapseExpand"
        config={{
          enable(editor) {
            const node = this.getItem(editor);
            if(! node) {return false;
            }
            const { type, collapsed } = node.getModel();
            const hasChild = node.getChildren().length > 0;
            returnnode && hasChild && type ! = ='case'&& collapsed; }}} / >,]; }}export default CustomCommand;
Copy the code

Modify the Mind folder and add the following code

import EditorCommand from '@/component/EditorCommand';

<Mind
  className={styles.mind}
  data={mindData}
  // rootShape="custom-root"
  firstSubShape="custom-node"
  secondSubShape="custom-node"
  graph={{
		// renderer: 'svg',
		fitView: 'cc', // the canvas shows the position,ccIs horizontally and vertically centered display //animate: true}} // Register shortcut keyshortcut={{
      append: false.appendChild: false.collaspeExpand: false.customAppendCase: true, / /EnterAdd at the same levelcase
      customAppendChildCase: true, / /TabAdd the lowercase
      customAppendCate: true, / / ⌘ +NAdd at the same levelcate
      customAppendChildCate: true, // ⌘ + ⇧ + NAdd the lowercate
      customCollapseExpand: true, / / ⌘ +BFold/unfoldcustomExpand: true, / / ⌘ +B / Ctrl + BancustomCollapse: true, / / ⌘ +B / Ctrl + BFolding}} / >{/* Custom shortcut events */}<EditorCommand />
Copy the code

3. Customize the right-click menu

Official documentation: ggeditor.com/docs/api/co…

Modify the MindContextMenu.js file under EditorContextMenu

import React from 'react';
import { NodeMenu, CanvasMenu, ContextMenu } from 'gg-editor';
import MenuItem from './MenuItem';
import styles from './index.less';

const MindContextMenu = (a)= >( <ContextMenu className={styles.contextMenu}> <NodeMenu> <MenuItem command="customAppendCase" icon="append" text="Enter Add peer case" /> <MenuItem command="customAppendChildCase" icon="append-child" text="Tab add child case" /> <MenuItem Command ="customAppendChildCate" icon="append" text="⌘ + N Add sibling cate" /> <MenuItem command="customAppendChildCate" Icon ="append-child" text="⌘ + ⇧ + N add subcate "/> <MenuItem command="customCollapse" icon="collapse" text="⌘ + B/Ctrl ⌘ + B fold "/> <MenuItem command="customExpand" icon="expand" text="⌘ + B/Ctrl + B expand" /> <MenuItem command="delete" Text ="Delete/BackSpace Delete "/> </NodeMenu> <CanvasMenu> <MenuItem command="undo" text=" undo" /> <MenuItem command="redo" Text =" redo "/> </CanvasMenu> </ContextMenu>); export default MindContextMenu;Copy the code

4. Customize the toolbar

Official documentation: ggeditor.com/docs/api/to…

Modify the mindToolanto.js file under EditorToolbar

import React from 'react';
import { Divider } from 'antd';
import { Toolbar } from 'gg-editor';
import ToolbarButton from './ToolbarButton';
import styles from './index.less';

const MindToolbar = (a)= >⌘ > <ToolbarButton Command ="undo" text="⌘ + Z revoke "/> <ToolbarButton Command ="redo" Text ="⇧ + ⌘ + Z redo "/> <Divider type="vertical" /> <ToolbarButton command="zoomIn" icon="zoom-in" text=" zoom" /> <ToolbarButton command="zoomOut" icon="zoom-out" text=" zoom "/> <ToolbarButton Command ="autoZoom" icon="fit-map" Text =" Divider type="vertical" /> <ToolbarButton Command ="resetZoom" icon="actual-size" text=" original proportion "/> <Divider type="vertical" /> <ToolbarButton command="customAppendCase" icon="append" text="Enter add the same case" /> <ToolbarButton Command ="customAppendChildCase" icon="append-child" text="Tab adds the subordinate case" /> <Divider type="vertical" /> <ToolbarButton Command ="customAppendChildCate" icon="append" text="⌘ + N add sibling cate" /> <ToolbarButton command="customAppendChildCate" Icon ="append-child" text="⌘ + ⇧ + N add subordinate cate "/> <Divider type="vertical" /> <ToolbarButton command="customCollapse" Icon ="collapse" text="⌘ + B/Ctrl + B fold "/> <ToolbarButton command="customExpand" icon="expand" text="⌘ + B/Ctrl + B Expand "/> </Toolbar>); export default MindToolbar;Copy the code

Iii. Final effect drawing

4. Reference links

• Project address: github.com/luchx/ECHI_…

• Frequently asked Questions: github.com/gaoli/GGEdi…

Other links

Wechat public account: Front-end miscellaneous