Использование AntV G6 для редактирования блок-схем | 🏆 Технический вопрос 3-й конкурс статей

визуализация данных

Этот призыв к статьям можно назвать судьбой, потому что в последние два месяца я работал над echarts, antvg6, cesium и другими визуальными широкоэкранными и блок-схемами, а также сделал некоторые резюме в блоге. Часть g6 уже была обобщена, это было в период исследования проекта на тот момент, а затем углубленное использование после перевода исследования в стадию разработки не было обобщено, а было только дальнейшее использование функции, и вещи, которые нужно использовать, все еще это. Во время своих исследований я написал демо, которое на тот момент находилось в стадии исследования, хотя было много проблем, я мог сослаться на структуру и использование.

Резюме личного блога:Энкаи просмотрел .top/blog/6.htm…

небольшая демонстрация g6:GitHub.com/encaike/G6-com…

представлять

G6 — это простой, удобный в использовании и полноценный движок для визуализации графов, который предоставляет серию элегантных и простых в использовании решений для визуализации графов, основанных на широких возможностях настройки. Это может помочь разработчикам создавать свои собственные приложения для визуализации графиков, анализа графиков или редактора графиков.

Установить

  • Введение CDN

    // version <= 3.2
    <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-{$version}/build/g6.js"></script>
    
    // version >= 3.3
    <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-{$version}/dist/g6.min.js"></script>
    

Введите номер версии в поле {$version}, например 3.4.7;

Последняя версия может просматривать последнюю версию и номер версии в NPM;

За подробностями обращайтесь к ветке Github:GitHub.com/ant-vis/ant-6/he…

  • установка нпм
  1. Установить зависимости с помощью npm

    npm install --save @antv/g6
    
  2. Импортируйте, где необходимо

    import G6 from '@antv/g6';
    
  3. Используется во Вью

    • Импорт в файлы, которые должны использовать G6/глобально в Vue
    • Способ создания схемы инициализации в методах, подробную настройку смотрите в документации
    • Вызовите метод init в смонтированном или созданном и используйте nextTick, чтобы немедленно изменить DOM.
    • Если есть Behavior, прослушивание привязки и т. д. интерактивные события, вам необходимо объявить их перед инициализацией.

Пример кода:

//此处为引入G6,以及引入我自己定义的行为和节点
import G6, { Graph } from "@antv/g6";
import { initBehavors } from "./behavior";
import { initItems } from "./Item";

//此处调用初始化方法
export default {
	created() {
    	this.$nextTick(() => {
          this.init();
        });
    },
    methods:{
    	init() {
        	/* 图初始化 */
            this.graph = new G6.Graph({
              container: "mountNode",         //这里写绑定标签的id,G6画布会插入到这个位置
              width: this.width,			  //图宽度
              height: this.height,            //图高度
              ...
            });
        }
    }
}

Если вы хотите изменить адаптивное окно, вам нужно одновременно изменить конфигурацию ширины и высоты холста и g6.

концепция

холст/макет

Канвас в G6 — это канвас, который нужно вставить в див, включая плагин канваса. Потому что вам нужно объявить класс и размер вставленного div во время инициализации. Макет — это правила распределения элементов холста, после настройки элементы будут фиксировать свое положение в соответствии с правилами распределения.

элемент

В G6 узлы, ребра и группы являются элементами, и структура является общей. Такие объекты определяются в данных графика и визуализируются на холсте методом рендеринга. Без событий взаимодействия, которые нужно координировать, элемент будет просто генерировать графику на холсте по определению.

условие

Состояние стиля элемента можно изменить с помощью интерактивного поведения и привязки событий, после чего отображаются эффекты стиля различных состояний, настроенных во время инициализации.

взаимодействовать

Интерактивные объекты в G6 в основном включают холст и элементы, которые могут реагировать на все обычные интерактивные события. Его необходимо определить и зарегистрировать в соответствии с поведением документа. Регистрация до инициализации может быть прописана в initialization, а регистрация после инициализации добавляется через метод. События также можно использовать отдельно, с привязкой слушателей.

анимация

Анимации можно добавлять в G6.После определения анимации и ее регистрации в компоненте анимации ее можно отобразить на холсте. В основном делится на глобальную анимацию, анимацию узлов и краевую анимацию.

использовать

Реализовать добавление узла/ребра/комбо (то же самое удаление и изменение)

  1. С помощью API G6 через метод addItem указывается тип элемента и создается конфигурация узла. Узлы, созданные таким образом, могут получать данные только через API.

    this.graph.addItem("node", {
        id: this.newNode.id,
        type: this.newNode.type,
        label: this.newNode.label,
        size: nodeSize,
        x: parseInt(this.newNode.x),
        y: parseInt(this.newNode.y),
        comboId: null
    });
    
  2. Определите данные графика в данных, затем добавьте их в массив узлов с помощью метода push и прочитайте данные через API для повторного рендеринга и добавления узлов. Узлы, созданные этим методом, могут получать операции непосредственно в данных.

    this.ghdata.nodes.push({
        id: this.newNode.id,
        type: this.newNode.type,
        label: this.newNode.label,
        size: nodeSize,
        x: parseInt(this.newNode.x),
        y: parseInt(this.newNode.y),
        comboId: null
    });
    this.graph.read(this.ghdata);
    this.graph.render();
    

Реализовать привязку поведения/мониторинг событий

  1. Зарегистрируйте поведение с помощью метода registerBehavior, параметрами которого являются имя поведения и конфигурация поведения, а метод getEvents предназначен для описания соответствующей связи между событиями и методами. После регистрации нужно настроить режим участия поведения в диаграмме, иначе он не подействует.

    G6.registerBehavior("before-edge", {
      getEvents() {
        return {
          "node:mousedown": "onMouseDown"
        };
      },
      onMouseDown(e) {
        const graph = this.graph;
        if ("index" in e.shape.attrs) {
          if (e.shape.attrs.index == "node") {
            console.log("节点:拖动关键图形");
          } else {
            console.log("节点:拖动锚点");
            if (e.item) {
              const point = e.item.getContainer().get("children")[
                parseInt(e.shape.attrs.index) + 1
              ];
              point.attr("fill", "#fff");
              point.attr("stroke", "#000");
              point.attr("r", 2.5);
            }
            const uid = Math.round(Math.random() * 100 + 100);
            graph.setMode("addedge");
            newEdge = graph.addItem("edge", {
              id: uid,
              type: "cubic",
              source: e.item.getModel().id,
              sourceAnchor: e.shape.attrs.index,
              target: { x: e.x, y: e.y }
            });
            newEdge.toBack();
          }
        } else {
          console.log("节点:拖动原生图形");
        }
      }
    });
    
    this.graph = new G6.Graph({
      modes: {
        default: [
          "before-edge"
        ]
      },
    })
    
  2. Добавляйте или удаляйте прослушиватели событий с помощью метода включения/выключения.Параметры — это события прослушивателя и методы обработки. Прослушиватели событий позволяют прослушивателям запускать только одно событие, но поведения могут прослушивать несколько событий одновременно.

    this.graph.on("node:click", e => {
      this.select = { ...e.item.getModel() };
    });
    

Реализовать переходы между состояниями

  1. Сначала объявите стиль состояния в конфигурации стиля, есть два метода:

    • Инициализировать глобальную конфигурацию по умолчанию
    this.graph = new G6.Graph({
      defaultEdge: {
        style: {
          stroke: "#000",
          endArrow: true
        }
      },
      edgeStateStyles: {
        hover: {
          stroke: "#66C4FF"
        }
      },
    })
    
    • Добавьте узлы для индивидуальной настройки
    this.graph.addItem("node", {
      ...,
      style: {
        stroke: "#000",
        endArrow: true
      }
    });
    
  2. Затем состояние элемента преобразуется через метод setItemState в коде.Многозначное преобразование поддерживается в старшей версии G6, то есть один тип состояния имеет несколько стилей состояния.

    const graph = this.graph;
    const item = e.item;
    graph.setItemState(item, "hover", true);
    

Реализовать пользовательские узлы/ребра

  1. Зарегистрируйте узел/ребро с помощью метода registerNode/registerEdge, параметрами которого являются имя узла/ребра и настраиваемый объект жизненного цикла.

    G6.registerNode(
      'nodeName',
      {
        options: {
          style: {},
          stateStyles: {
            hover: {},
            selected: {},
          },
        },
        /**
         * 绘制节点,包含文本
         * @param  {Object} cfg 节点的配置项
         * @param  {G.Group} group 节点的容器
         * @return {G.Shape} 返回一个绘制的图形作为 keyShape,通过 node.get('keyShape') 可以获取。
         * 关于 keyShape 可参考文档 核心概念-节点/边/Combo-图形 Shape 与 keyShape
         */
        draw(cfg, group) {},
        /**
         * 绘制后的附加操作,默认没有任何操作
         * @param  {Object} cfg 节点的配置项
         * @param  {G.Group} group 节点的容器
         */
        afterDraw(cfg, group) {},
        /**
         * 更新节点,包含文本
         * @override
         * @param  {Object} cfg 节点的配置项
         * @param  {Node} node 节点
         */
        update(cfg, node) {},
        /**
         * 更新节点后的操作,一般同 afterDraw 配合使用
         * @override
         * @param  {Object} cfg 节点的配置项
         * @param  {Node} node 节点
         */
        afterUpdate(cfg, node) {},
        /**
         * 响应节点的状态变化。
         * 在需要使用动画来响应状态变化时需要被复写,其他样式的响应参见下文提及的 [配置状态样式] 文档
         * @param  {String} name 状态名称
         * @param  {Object} value 状态值
         * @param  {Node} node 节点
         */
        setState(name, value, node) {},
        /**
         * 获取锚点(相关边的连入点)
         * @param  {Object} cfg 节点的配置项
         * @return {Array|null} 锚点(相关边的连入点)的数组,如果为 null,则没有控制点
         */
        getAnchorPoints(cfg) {},
      },
      // 继承内置节点类型的名字,例如基类 'single-node',或 'circle', 'rect' 等
      // 当不指定该参数则代表不继承任何内置节点类型
      extendedNodeName,
    );
    
  • Метод draw требуется, если новый узел не расширяется из любого существующего узла или из «одного узла»;
  • Вся графика внутри узла использует систему координат относительно самого узла, т. е. (0, 0) — это центр узла. Координаты узла относятся к холсту и контролируются матрицей на группе узлов.В пользовательском узле восприятие пользователя не требуется. Если вы добавляете прямоугольную форму в пользовательский узел, обратите внимание на его x и y минус половина его длины и ширины. См. пример определения узлов с нуля;
  • Метод обновления можно оставить неопределенным:
    • Когда обновление не определено: если указан третий параметр extendedNodeName для registerNode (то есть он представляет собой наследование указанного встроенного типа узла), логика обновления унаследованного встроенного типа узла будет выполняться при обновлении узла; если в третьем параметре registerNode не указан параметр, то при обновлении узла будет выполнен метод отрисовки, и вся графика будет очищена и перерисована;
    • Когда метод обновления определен, независимо от того, указан ли третий параметр registerNode, переопределенная логика функции обновления будет выполняться при обновлении узла.
  • методы afterDraw и afterUpdate обычно используются для расширения существующих узлов, таких как: прикрепление изображений к прямоугольным узлам, добавление анимации к круглым узлам и т. д.;
  • setState нужно перезаписывать только тогда, когда вам нужно использовать анимацию для ответа на изменения состояния.Общий стиль реакции на изменения состояния может быть достигнут путем настройки стилей состояния;
  • Метод getAnchorPoints нужно переопределять только в том случае, если вам нужно ограничить точки подключения краем, или его можно указать непосредственно в данных.
  1. В дополнение к draw, который является обязательным методом рисования графики, по мере необходимости добавляются другие методы (методы, которые не переопределены, будут наследовать базовый класс). Параметр cfg Draw — это конфигурация создания, а группа — комбинация графики, этот метод возвращает комбинацию графики, которая является окончательным стилем.

    G6.registerNode("ownrect", {
      draw(cfg, group) {
        group.addShape("rect", {
          attrs: {
            width: cfg.size[0],
            height: cfg.size[1],
            stroke: "block",
            fill: "white",
            index: "node"
          },
          draggable: true
        });
        const points = cfg.anchorPoints;
        for (let index = 0; index < points.length; index++) {
          group.addShape("circle", {
            attrs: {
              x: cfg.size[0] * points[index][0],
              y: cfg.size[1] * points[index][1],
              r: 2.5,
              stroke: "block",
              fill: "white",
              index
            }
          });
        }
        if (cfg.label) {
          group.addShape("text", {
            attrs: {
              x: cfg.size[0] / 2,
              y: cfg.size[1] / 2,
              textAlign: "center",
              textBaseline: "middle",
              text: cfg.label,
              fill: "#666",
              index: "node"
            },
            draggable: true
          });
        }
        return group;
      }
    });
    

Реализовать перетаскивание для создания узлов

  1. Установите тег HTML для перетаскивания, а затем добавьте метод перетаскивания после завершения перетаскивания.

    <span draggable @dragend="handleDragEnd">
      拖动节点
    </span>
    
  2. Задайте сгенерированные координаты x, y и конфигурацию узлов в dragend, а затем добавьте узлы на холст через addItem.

    handleDragEnd(e) {
      let str = Math.round(Math.random() * 100).toString();
      let point = this.graph.getPointByClient(e.clientX, e.clientY);
      this.graph.addItem("node", {
        id: str,
        type: "ownrect",
        label: str,
        size: [80, 80],
        x: parseInt(point.x - 40),
        y: parseInt(point.y - 40),
        comboId: null
      });
    },
    

Реализовать перетаскивание для создания краев

  1. Сначала разберитесь с логикой перетаскивания и добавления ребер и перетаскивания и перемещения узлов.Поведение перетаскивания узла помещается в режим по умолчанию, а поведение добавления ребра помещается в пользовательский режим. Через событие node:mousedown (мышь вниз на узле) вы можете отслеживать, нажата ли мышь на точку привязки или на узел.Если это точка привязки, переключитесь в пользовательский режим и создайте ребро в том же время.Если это узел, все равно.

    G6.registerBehavior("before-edge", {
      getEvents() {
        return {
          "node:mousedown": "onMouseDown"
        };
      },
      onMouseDown(e) {
        const graph = this.graph;
        if ("index" in e.shape.attrs) {
          if (e.shape.attrs.index == "node") {
            console.log("节点:拖动关键图形");
          } else {
            console.log("节点:拖动锚点");
            if (e.item) {
              const point = e.item.getContainer().get("children")[
                parseInt(e.shape.attrs.index) + 1
              ];
              point.attr("fill", "#fff");
              point.attr("stroke", "#000");
              point.attr("r", 2.5);
            }
            const uid = Math.round(Math.random() * 100 + 100);
            graph.setMode("addedge");
            newEdge = graph.addItem("edge", {
              id: uid,
              type: "cubic",
              source: e.item.getModel().id,
              sourceAnchor: e.shape.attrs.index,
              target: { x: e.x, y: e.y }
            });
            newEdge.toBack();
          }
        } else {
          console.log("节点:拖动原生图形");
        }
      }
    });
    
  2. После переключения в пользовательский режим прослушивайте событие mousemove (перемещение мыши по всему холсту) и постоянно обновляйте целевую конфигурацию стороны, получая положение мыши, чтобы заставить мышь перетаскивать и перемещать ее во время движения. Затем, когда инициируется событие mouseup (полное поднятие мыши на холсте), получается информация об узле и точке привязки, на которую указывалось при освобождении, и создается ребро обновления. Если вы не находитесь на узле, когда отпускаете мышь, край сразу уничтожается.

    G6.registerBehavior("add-edge", {
      getEvents() {
        return {
          mousemove: "onMouseMove",
          mouseup: "onMouseUp"
        };
      },
      onMouseMove(e) {
        const graph = this.graph;
        console.log("画布:鼠标移动");
        if (newEdge) {
          graph.updateItem(newEdge, {
            target: { x: e.x, y: e.y }
          });
        }
      },
      onMouseUp(e) {
        const graph = this.graph;
        if (!e.item.getModel()) {
          console.log("节点:松开在画布");
          graph.removeItem(newEdge);
          graph.setMode("default");
          newEdge = {};
          return;
        }
        if ("index" in e.shape.attrs) {
          if (e.shape.attrs.index == "node") {
            console.log("节点:松开在关键图形");
          } else {
            console.log("节点:松开在锚点");
            graph.updateItem(newEdge, {
              target: e.item.getModel().id,
              targetAnchor: e.shape.attrs.index
            });
            graph.setMode("default");
            newEdge = {};
          }
        } else {
          console.log("节点:松开在原生图形");
        }
      }
    });
    

наконец

Это мой первый раз, когда писать статью, так что есть много недостатков, я надеюсь, что все могут критиковать и исправить меня, спасибо ~

🏆 Технический выпуск 3 | Все о визуализации данных...