<!--
Copyright 2019-2021 Huawei Technologies Co., Ltd.All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<template>
  <!--cl-graph-manage -->
  <div class="cl-graph-manage">
    <div class="graph-p32">
      <div class="guide-content"
           v-if="guide.show">
        <el-popover placement="top-start"
                    ref="popover"
                    :style="{ position: 'absolute', top: guide.top, left: guide.left }"
                    :title="guide.title"
                    width="370"
                    v-model="guide.show">
          <i class="el-icon-close"
             @click="closeUserGuide"></i>
          <div v-for="item in guide.content"
               :key="item"
               class="guide-span">{{ item }}</div>
          <div class="step-pic">
            <img :src="require(`@/assets/images/graph-stepimg${guide.step}.svg`)" />
          </div>

          <el-button type="primary"
                     @click="guideNext">{{
            guide.step === 3 ? $t('graph.finish') : $t('graph.next')
          }}</el-button>
        </el-popover>
        <div class="step"
             v-show="guide.step == 1">
          <img :src="require(`@/assets/images/graph-step1${language === 'en-us' ? '-en' : ''}.svg`)"
               alt="" />
        </div>
        <div class="step"
             v-show="guide.step == 2">
          <img :src="require(`@/assets/images/graph-step2${language === 'en-us' ? '-en' : ''}.svg`)"
               alt="" />
        </div>
        <div class="step"
             v-show="guide.step == 3">
          <img :src="require(`@/assets/images/graph-step3${language === 'en-us' ? '-en' : ''}.svg`)"
               alt="" />
        </div>
      </div>
      <div class="cl-title cl-graph-title">
        <div class="cl-title-left">
          {{ $t('graph.titleText') }}
          <div class="path-message">
            <span>{{$t('symbols.leftbracket')}}</span>
            <span>{{$t('trainingDashboard.summaryDirPath')}}</span>
            <span>{{summaryPath}}</span>
            <span>{{$t('symbols.rightbracket')}}</span>
          </div>
          <span @click="showUserGuide"
                class="guide">
            <i class="guide-icon"></i>
            {{$t('graph.guide')}}
          </span>
        </div>
        <div class="cl-title-right">
          <div class="cl-close-btn"
               @click="jumpToTrainDashboard">
            <img src="@/assets/images/close-page.png" />
          </div>
        </div>
      </div>
      <div class="cl-content">
        <div id="graphs">
          <div class="cl-graph"
               :class="fullScreen ? 'full-screen' : ''">
            <!-- graph -->
            <div class="graph-container"
                 :class="rightShow ? '' : 'all'">
              <!-- No data is displayed. -->
              <div class="image-noData"
                   v-if="!loading.show && !Object.keys(allGraphData).length">
                <div>
                  <img :src="require('@/assets/images/nodata.png')"
                       alt="" />
                </div>
                <div class="noData-text">{{ $t('public.noData') }}</div>
              </div>
              <!-- Operation button column -->
              <div class="operate-button-list">
                <!-- Download button. -->
                <div :title="$t('graph.fullScreen')"
                     class="full-screen-button"
                     @click="toggleScreen"></div>
                <div :title="$t('graph.downloadPic')"
                     class="download-button"
                     @click="downLoadSVG"></div>
              </div>
              <div id="graph"
                   class="graph"
                   v-loading.fullscreen.lock="loading.show"
                   element-loading-background="rgba(0, 0, 0, 0.3)"
                   :element-loading-text="loading.info"></div>
            </div>
            <!-- Right column -->
            <div id="sidebar"
                 :class="rightShow ? '' : 'right-hide'">
              <div class="toggle-right"
                   @click="toggleRight">
                <i :class="rightShow ? 'icon-toggle' : 'icon-toggle icon-left'"></i>
              </div>
              <!-- Search box -->
              <div class="sidebar-tooltip">
                <el-tooltip placement="top"
                            effect="light">
                  <div slot="content"
                       class="tooltip-container">
                    <div class="cl-graph-sidebar-tip">
                      {{$t('graph.sidebarTip')}}
                    </div>
                  </div>
                  <i class="el-icon-info"></i>
                </el-tooltip>
              </div>
              <el-select @change="fileChange"
                         @visible-change="getSelectList"
                         :popper-append-to-body="false"
                         class="search"
                         v-model="fileSearchBox.value">
                <el-option v-for="item in fileSearchBox.suggestions"
                           :key="item.value"
                           :title="item.value"
                           :label="item.value"
                           :value="item.value">
                </el-option>
              </el-select>
              <!-- Search box -->
              <div class="search-wrap">
                <el-input class="search"
                          :placeholder="$t('graph.inputNodeName')"
                          v-model="searchBox.value"
                          @input="filterChange"
                          @keyup.enter.native="searchNodesNames"
                          clearable>
                  <el-button slot="append"
                             @click="treeWrapFlag=!treeWrapFlag"
                             class="collapse_i">
                    <i class="el-icon-caret-bottom"
                       v-if="!treeWrapFlag"></i>
                    <i class="el-icon-caret-top"
                       v-else></i>
                  </el-button>
                </el-input>
                <div class="tree-wrap"
                     v-show="treeWrapFlag">
                  <el-tree v-show="treeFlag"
                           :props="props"
                           :load="loadNode"
                           @node-collapse="nodeCollapse"
                           @node-click="handleNodeClick"
                           node-key="name"
                           :expand-on-click-node="false"
                           :lazy="true"
                           :highlight-current="true"
                           ref="tree">
                    <span class="custom-tree-node"
                          slot-scope="{ node ,data }">
                      <span>
                        <img v-if="data.type ==='name_scope'"
                             :src="require('@/assets/images/name-scope.svg')"
                             class="image-type" />
                        <img v-else-if="data.type ==='Const'"
                             :src="require('@/assets/images/constant-node.svg')"
                             class="image-type" />
                        <img v-else-if="data.type ==='aggregation_scope'"
                             :src="require('@/assets/images/polymetric.svg')"
                             class="image-type" />
                        <img v-else
                             :src="require('@/assets/images/operator-node.svg')"
                             class="image-type" />
                      </span>
                      <span class="custom-tree-node">{{ node.label }}</span>
                    </span>
                  </el-tree>
                  <el-tree v-show="!treeFlag"
                           :props="defaultProps"
                           :load="loadSearchNode"
                           :lazy="true"
                           node-key="name"
                           @node-click="handleNodeClick"
                           :expand-on-click-node="false"
                           ref="searchTree">
                    <span class="custom-tree-node"
                          slot-scope="{ node ,data }">
                      <span>
                        <img v-if="data.type ==='name_scope'"
                             :src="require('@/assets/images/name-scope.svg')"
                             class="image-type" />
                        <img v-else-if="data.type ==='Const'"
                             :src="require('@/assets/images/constant-node.svg')"
                             class="image-type" />
                        <img v-else-if="data.type ==='aggregation_scope'"
                             :src="require('@/assets/images/polymetric.svg')"
                             class="image-type" />
                        <img v-else
                             :src="require('@/assets/images/operator-node.svg')"
                             class="image-type" />
                      </span>
                      <span class="custom-tree-node">{{ node.label }}</span>
                    </span>
                  </el-tree>
                </div>
              </div>
              <!-- Functional Area -->
              <div id="small-container">
                <div id="small-resize">
                  <div id="small-map"></div>
                  <div id="inside-box"></div>
                </div>
              </div>
              <!-- Node information -->
              <div :class="
                  showLegend
                    ? 'node-info-con node-info-container'
                    : 'node-info-con node-info-container-long'
                ">
                <div class="title">
                  {{ $t('graph.nodeInfo') }}
                  <img :src="require('@/assets/images/all-drop-down.png')"
                       fun="fold"
                       hidden
                       alt="" />
                  <img :src="require('@/assets/images/all-uptake.png')"
                       fun="fold"
                       hidden
                       alt="" />
                </div>
                <div class="node-info"
                     v-show="selectedNode.show">
                  <div class="items">
                    <div class="label item">{{ $t('graph.name') }}</div>
                    <div class="value">
                      <span class="cl-display-block"
                            @dblclick="nodeNameClick">{{ selectedNode.title }}</span>
                    </div>
                  </div>
                  <div class="items"
                       v-if="selectedNode.countShow">
                    <div class="label item">{{ $t('graph.count') }}</div>
                    <div class="value items-over">{{ selectedNode.count }}</div>
                  </div>
                  <div class="items"
                       v-if="!selectedNode.countShow">
                    <div class="label item">{{ $t('graph.type') }}</div>
                    <div class="value items-over">{{ selectedNode.type }}</div>
                  </div>
                  <div class="items itemHeight"
                       v-if="!selectedNode.countShow">
                    <div class="item">
                      {{ $t('graph.attr') }} ({{
                        selectedNode.info.attributes.length
                      }})
                    </div>
                  </div>
                  <ul v-if="selectedNode.info && !selectedNode.countShow"
                      class="item-content hover"
                      :class="
                      selectedNode.info.attributes.length > 2
                        ? 'item-min2'
                        : selectedNode.info.attributes.length > 0
                        ? 'item-min'
                        : ''
                    ">
                    <li v-for="item in selectedNode.info.attributes"
                        :key="item.name">
                      <div class="key">
                        {{ item.name }}
                      </div>
                      <div class="input cl-input-value">
                        <pre>{{ item.value }}</pre>
                      </div>
                    </li>
                  </ul>
                  <div class="items itemHeight">
                    <div class="item">
                      {{ $t('graph.inputs') }} (
                      {{
                        selectedNode.info.input.length +
                          selectedNode.info.inputControl.length
                      }})
                    </div>
                  </div>
                  <ul v-if="selectedNode.info"
                      class="item-content hover"
                      :class="
                      selectedNode.info.input.length > 1
                        ? 'item-min2'
                        : selectedNode.info.input.length > 0
                        ? 'item-min'
                        : ''
                    ">
                    <li v-for="item in selectedNode.info.input"
                        :key="item.$index"
                        @click="querySingleNode({ value: item.name })"
                        class="pointer">
                      <div class="input">{{ item.name }}</div>
                      <div class="size">{{ item.value }}</div>
                      <div class="clear"></div>
                    </li>
                    <li class="control-list"
                        v-if="
                        selectedNode.info &&
                          selectedNode.info.inputControl.length
                      ">
                      <div class="dependence-title"
                           @click="toggleControl('input')"
                           :class="selectedNode.showControl.input ? '' : 'hide'">
                        <img :src="require('@/assets/images/all-uptake.png')"
                             alt="" />
                        {{ $t('graph.controlDependencies') }}
                      </div>
                      <ul v-show="selectedNode.showControl.input">
                        <li v-for="item in selectedNode.info.inputControl"
                            :key="item.$index"
                            @click="querySingleNode({ value: item.name })"
                            class="pointer">
                          <div class="input">{{ item.name }}</div>
                          <div class="size">{{ item.value }}</div>
                          <div class="clear"></div>
                        </li>
                      </ul>
                    </li>
                  </ul>
                  <div class="items">
                    <div class="item">
                      {{ $t('graph.outputs') }} (
                      {{
                        selectedNode.info.output.length +
                          selectedNode.info.outputControl.length
                      }})
                    </div>
                  </div>
                  <ul v-if="selectedNode.info"
                      class="item-content hover"
                      :class="
                      selectedNode.info.output.length > 1
                        ? 'item-min2'
                        : selectedNode.info.output.length > 0
                        ? 'item-min'
                        : ''
                    ">
                    <li v-for="item in selectedNode.info.output"
                        :key="item.$index"
                        @click="querySingleNode({ value: item.name })"
                        class="pointer">
                      <div class="input">{{ item.name }}</div>
                      <div class="size">{{ item.value }}</div>
                      <div class="clear"></div>
                    </li>
                    <li class="control-list"
                        v-if="
                        selectedNode.info &&
                          selectedNode.info.outputControl.length
                      ">
                      <div class="dependence-title"
                           @click="toggleControl('output')"
                           :class="selectedNode.showControl.output ? '' : 'hide'">
                        <img :src="require('@/assets/images/all-uptake.png')"
                             alt="" />
                        {{ $t('graph.controlDependencies') }}
                      </div>
                      <ul v-show="selectedNode.showControl.output">
                        <li v-for="item in selectedNode.info.outputControl"
                            :key="item.$index"
                            @click="querySingleNode({ value: item.name })"
                            class="pointer">
                          <div class="input">{{ item.name }}</div>
                          <div class="size">{{ item.value }}</div>
                          <div class="clear"></div>
                        </li>
                      </ul>
                    </li>
                  </ul>
                  <div class="items"
                       v-if="selectedNode.info && selectedNode.info.output_i !== 0">
                    <div class="label item">{{ $t('graph.outputs_i') }}</div>
                    <span class="value">{{ selectedNode.info.output_i }}</span>
                  </div>
                </div>
              </div>
              <!-- Legend -->
              <div class="legend"
                   v-if="!fullScreen">
                <div class="title">
                  {{ $t('graph.legend') }}
                  <img :src="require('@/assets/images/all-drop-down.png')"
                       v-show="!showLegend"
                       @click="foldLegend"
                       alt="" />
                  <img :src="require('@/assets/images/all-uptake.png')"
                       v-show="showLegend"
                       @click="foldLegend"
                       alt="" />
                </div>
                <div v-show="showLegend"
                     class="legend-content">
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/name-scope.svg')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.nameSpace')">
                      {{ $t('graph.nameSpace') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/polymetric.svg')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.polymetric')">
                      {{ $t('graph.polymetric') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/virtual-node.svg')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.virtualNode')">
                      {{ $t('graph.virtualNode') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/operator-node.svg')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.operatorNode')">
                      {{ $t('graph.operatorNode') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/constant-node.svg')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.constantNode')">
                      {{ $t('graph.constantNode') }}
                    </div>
                  </div>
                  <br>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/data-flow.png')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.dataFlowEdge')">
                      {{ $t('graph.dataFlowEdge') }}
                    </div>
                  </div>
                  <div class="legend-item">
                    <div class="pic">
                      <img :src="require('@/assets/images/control-dep.png')"
                           alt="" />
                    </div>
                    <div class="legend-text"
                         :title="$t('graph.controllDepEdge')">
                      {{ $t('graph.controllDepEdge') }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import CommonProperty from '@/common/common-property.js';
import RequestService from '@/services/request-service';
import {select, selectAll, zoom} from 'd3';
import 'd3-graphviz';
const d3 = {select, selectAll, zoom};
import commonGraph from '../../mixins/common-graph.vue';
import smallMap from '../../mixins/small-map.vue';

export default {
  mixins: [commonGraph, smallMap],
  data() {
    return {
      summaryPath: this.$route.query.summaryPath,
      graph: {}, // Basic information about graph0 in svg
      svg: {}, // Basic information about svg

      allGraphData: {}, // graph Original input data
      firstFloorNodes: [], // ID array of the first layer node.
      // Information about the selected node
      selectedNode: {
        info: {
          inputControl: [],
          input: [],
          outputControl: [],
          output: [],
          attributes: [],
        },
        showControl: {
          input: true,
          output: true,
        },
      },
      // Training job id
      trainJobID: '',
      nodesCountLimit: 1500, // Maximum number of sub-nodes in a namespace.
      maxChainNum: 70,

      // Node search box
      searchBox: {
        value: '',
        suggestions: [],
      },
      fileSearchBox: {
        value: '',
        suggestions: [],
      },
      showLegend: true, // Display Legend
      // Controls whether the loading is displayed.
      loading: {
        show: false,
        info: '',
      },
      scaleRange: [0.001, 1000], // graph zooms in and zooms out.
      rightShow: true, // Check whether the right side bar is displayed.
      fullScreen: false, // Display Full Screen
      graphviz: null,
      graphvizTemp: null,
      initOver: false,
      guide: {
        show: false,
        step: 1,
        top: '0%',
        left: '0%',
        content: [
          this.$t('graph.guideContent11'),
          this.$t('graph.guideContent12'),
          this.$t('graph.guideContent13'),
          this.$t('graph.guideContent14'),
        ],
        title: '',
      },
      language: '',
      defaultProps: {
        children: 'nodes',
        label: 'label',
        isLeaf: 'leaf',
      },
      treeFlag: true,
      props: {
        label: 'label',
        children: 'children',
        isLeaf: 'leaf',
      },
      node: null,
      resolve: null,
      treeWrapFlag: true,
      searchNode: null,
      searchResolve: null,
      isIntoView: true,
    };
  },
  computed: {},
  watch: {
    guide: {
      handler(newVal) {
        if (newVal.step === 1) {
          this.guide.top = '20%';
          this.guide.left = '48%';
        } else if (newVal.step === 2) {
          this.guide.top = '47%';
          this.guide.left = '62%';
        } else if (newVal.step === 3) {
          this.guide.top = '45%';
          this.guide.left = '52%';
        }
        this.guide.title = this.$t(`graph.guideTitle${newVal.step}`);
      },
      deep: true,
    },
  },
  mounted() {
    // Judging from the training job overview.
    if (!this.$route.query || !this.$route.query.train_id) {
      this.trainJobID = '';
      this.$message.error(this.$t('trainingDashboard.invalidId'));
      document.title = `${this.$t('graph.titleText')}-MindInsight`;
      return;
    }
    const showGuide = window.localStorage.getItem('graphShowGuide');
    if (!showGuide) {
      this.guide.show = true;
      window.localStorage.setItem('graphShowGuide', true);
    }

    this.trainJobID = this.$route.query.train_id;

    this.language = window.localStorage.getItem('milang');
    const languageList = ['zh-cn', 'en-us'];
    if (!this.language || !languageList.includes(this.language)) {
      this.language = languageList[0];
      window.localStorage.setItem('milang', this.language);
    }

    document.title = `${decodeURIComponent(this.trainJobID)}-${this.$t(
        'graph.titleText',
    )}-MindInsight`;
    this.getDatavisualPlugins();
    window.onresize = () => {
      if (this.graph.dom) {
        this.$nextTick(() => {
          setTimeout(() => {
            this.initSvg(false);
            this.initGraphRectData();
          }, 500);
        });
      }
    };
  },
  destroyed() {
    window.onresize = null;
  },
  methods: {
    /**
     * Tree linkage with graph  Expand of current node
     * @param {Obeject} nodes Data of children of current node
     * @param {Obeject} name  The name of the current node
     */
    nodeExpandLinkage(nodes, name) {
      const curNodeData = nodes.map((val) => {
        return {
          label: val.name.split('/').pop(),
          ...val,
        };
      });
      const node = this.$refs.tree.getNode(name);
      curNodeData.forEach((val) => {
        this.$refs.tree.append(val, name);
      });
      node.childNodes.forEach((val) => {
        if (
          val.data.type !== 'name_scope' &&
          val.data.type !== 'aggregation_scope'
        ) {
          val.isLeaf = true;
        }
      });
      node.expanded = true;
      node.loading = false;
      this.$refs.tree.setCurrentKey(name);
      this.$nextTick(() => {
        setTimeout(() => {
          const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
          if (dom && this.rightShow) {
            dom.scrollIntoView();
          }
        }, 800);
      });
    },
    /**
     * Collapse node
     * @param {Object} _
     * @param {Object} node node data
     */
    nodeCollapse(_, node) {
      node.loaded = false;
      if (this.treeFlag) {
        this.dealDoubleClick(node.data.name);
      }
    },
    /**
     * Draw the tree
     * @param {Object} node tree root node
     * @param {Function} resolve callback function ,return next node data
     */
    loadNode(node, resolve) {
      if (node.level === 0) {
        node.childNodes = [];
        if (!this.node && !this.resolve) {
          this.node = node;
          this.resolve = resolve;
        }
      } else if (node.level >= 1) {
        this.isIntoView = false;
        this.loading.info = this.$t('graph.queryLoading');
        this.loading.show = true;
        setTimeout(() => {
          this.queryGraphData(node.data.name, resolve);
        }, 200);
      }
    },
    /**
     * Draw the tree
     * @param {Object} node tree root node
     * @param {Function} resolve callback function ,return next node data
     */
    loadSearchNode(node, resolve) {
      if (node.level === 0) {
        node.childNodes = [];
        if (!this.searchNode && !this.searchResolve) {
          this.searchNode = node;
          this.searchResolve = resolve;
        }
      } else if (node.level >= 1) {
        const params = {
          name: node.data.name,
          train_id: this.trainJobID,
          tag: this.fileSearchBox.value,
        };
        if (node.childNodes && node.childNodes.length) {
          node.expanded = true;
          node.loaded = true;
          node.loading = false;
          return;
        }
        RequestService.queryGraphData(params).then((res) => {
          if (res && res.data && res.data.nodes) {
            const data = res.data.nodes.map((val) => {
              return {
                label: val.name.split('/').pop(),
                leaf:
                  val.type === 'name_scope' || val.type === 'aggregation_scope'
                    ? false
                    : true,
                ...val,
              };
            });
            resolve(data);
          }
        });
      }
    },
    /**
     * Deal search data
     * @param {Array} arr search tree data
     */
    dealSearchResult(arr) {
      arr.forEach((val) => {
        if (val.nodes) {
          this.dealSearchResult(val.nodes);
        }
        val.label = val.name.split('/').pop();
      });
    },
    filterChange() {
      if (this.searchBox.value === '') {
        this.treeFlag = true;
        this.$nextTick(() => {
          setTimeout(() => {
            const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
            if (dom && this.rightShow) {
              dom.scrollIntoView();
            }
          }, 800);
        });
      }
    },
    handleNodeClick(data) {
      this.isIntoView = false;
      this.selectedNode.name = data.name;
      if (this.treeFlag) {
        this.selectNode(true);
      } else {
        this.querySingleNode({value: data.name});
      }
    },
    /**
     * Add the location attribute to each node to facilitate the obtaining of node location parameters.
     */
    afterInitGraph() {
      setTimeout(() => {
        if (this.graphviz) {
          this.graphviz._data = null;
          this.graphviz._dictionary = null;
          this.graphviz = null;
        }

        if (this.graphvizTemp) {
          this.graphvizTemp._data = null;
          this.graphvizTemp._dictionary = null;
          this.graphvizTemp = null;
        }
      }, 100);
      this.fitGraph('graph');
      this.transplantChildrenDom();
      const svg = document.querySelector('#subgraphTemp svg');
      if (svg) {
        svg.remove();
      }
      this.$nextTick(() => {
        this.loading.show = false;
      });
      const elements = d3.select('#graph').selectAll('g.node, g.edge').nodes();
      elements.forEach((ele) => {
        if (!ele.hasAttribute('transform')) {
          ele.setAttribute('transform', 'translate(0,0)');
        }
        // The title value needs to be manually set for the virtual node.
        if (Array.prototype.includes.call(ele.classList, 'plain')) {
          const title = ele.querySelector('title');
          title.textContent = title.textContent.split('^')[0];
        }
      });
      d3.selectAll('g.edge>title').remove();
      // The graph generated by the plug-in has a useless title and needs to be deleted.
      document.querySelector('#graph g#graph0 title').remove();
      this.initGraphRectData();
      this.startApp();
    },
    /**
     * Initialization method executed after the graph rendering is complete
     */
    startApp() {
      const nodes = d3.selectAll('g.node, g.cluster');
      nodes.on(
          'click',
          (target, index, nodesList) => {
            this.clickEvent(target, index, nodesList, 'graph');
          },
          false,
      );
      // namespaces Expansion or Reduction
      nodes.on(
          'dblclick',
          (target, index, nodesList) => {
            this.dblclickEvent(target, index, nodesList, 'graph');
          },
          false,
      );
      this.initZooming('graph');
      if (this.selectedNode.name) {
        this.selectNode(true);
      }
    },
    /**
     * Double-click the processing to be performed on the node to expand or narrow the namespace or aggregation node.
     * @param {String} name Name of the current node (also the ID of the node)
     */
    dealDoubleClick(name) {
      this.loading.info = this.$t('graph.queryLoading');
      this.loading.show = true;
      this.$nextTick(() => {
        // DOM tree needs time to respond, otherwise the loading icon will not be displayed
        const timeOut = 500;
        setTimeout(() => {
          name = name.replace('_unfold', '');
          if (this.allGraphData[name].isUnfold) {
            this.selectedNode.name = name;
            this.deleteNamespace(name);
          } else {
            this.queryGraphData(name);
          }
        }, timeOut);
      });
    },
    /**
     * To obtain graph data, initialize and expand the namespace or aggregate nodes.
     * @param {String} name Name of the current node.
     * @param {Function} resolve Callback function.
     */
    queryGraphData(name, resolve) {
      const namescopeChildLimit = 3500;
      const independentLayout = this.allGraphData[name]
        ? this.allGraphData[name].independent_layout
        : false;
      const params = {
        name: name,
        train_id: this.trainJobID,
        tag: this.fileSearchBox.value,
      };
      this.loading.info = this.$t('graph.queryLoading');
      this.loading.show = true;
      RequestService.queryGraphData(params)
          .then(
              (response) => {
                if (response && response.data && response.data.nodes) {
                  // If the namespace to be expanded is larger than the maximum number of subnodes,
                  // an error is reported and the namespace is highlighted.
                  const nodesCountLimit = name
                ? this.nodesCountLimit
                : namescopeChildLimit;
                  if (
                    !independentLayout &&
                response.data.nodes.length > nodesCountLimit
                  ) {
                    this.$message.error(this.$t('graph.tooManyNodes'));
                    this.packageDataToObject(name, false);
                    this.loading.show = false;
                    this.$refs.tree.getNode(name).loading = false;
                  } else {
                    const nodes = JSON.parse(JSON.stringify(response.data.nodes));
                    if (nodes && nodes.length) {
                      this.packageDataToObject(name, true, nodes);
                      // If the name is empty, it indicates the outermost layer.
                      if (!name) {
                        const dot = this.packageGraphData();
                        this.initGraph(dot);
                      } else {
                        if (this.allGraphData[name].type === 'aggregation_scope') {
                          this.dealAggregationNodes(name);
                          if (
                            this.allGraphData[name].maxChainNum > this.maxChainNum
                          ) {
                            this.$message.error(this.$t('graph.tooManyChain'));
                            this.allGraphData[name].isUnfold = true;
                            this.selectedNode.name = name;
                            this.loading.show = false;
                            this.deleteNamespace(name);
                            this.$refs.tree.getNode(name).loading = false;
                            return;
                          }
                        }
                        this.allGraphData[name].isUnfold = true;
                        this.selectedNode.name = `${name}_unfold`;
                        this.layoutNamescope(name, true);
                      }
                    } else {
                      this.initGraphRectData();
                      this.loading.show = false;
                    }
                    const data = response.data.nodes.map((val) => {
                      return {
                        label: val.name.split('/').pop(),
                        leaf:
                      val.type === 'name_scope' ||
                      val.type === 'aggregation_scope'
                        ? false
                        : true,
                        ...val,
                      };
                    });
                    if (name) {
                      if (resolve) {
                        resolve(JSON.parse(JSON.stringify(data)));
                      } else {
                        this.nodeExpandLinkage(response.data.nodes, name);
                      }
                    } else {
                      this.node.childNodes = [];
                      this.resolve(JSON.parse(JSON.stringify(data)));
                    }
                  }
                }
              },
              (error) => {
                this.loading.show = false;
              },
          )
          .catch((error) => {
          // A non-Google Chrome browser may not work properly.
            this.loading.show = false;
            if (error && error.includes('larger than maximum 65535 allowed')) {
              this.$message.error(this.$t('graph.dataTooLarge'));
            } else {
              this.$bus.$emit('showWarmText', true);
            }
            if (name && this.allGraphData[name]) {
              this.allGraphData[name].isUnfold = false;
              this.allGraphData[name].children = [];
              this.allGraphData[name].size = [];
              this.allGraphData[name].html = '';
            }
          });
    },
    /**
     * To obtain datavisual plugins
     */
    getDatavisualPlugins() {
      const params = {
        train_id: this.trainJobID,
      };
      RequestService.getDatavisualPlugins(params)
          .then((res) => {
            this.fileSearchBox.suggestions = [];
            if (
              !res ||
            !res.data ||
            !res.data.plugins ||
            !res.data.plugins.graph ||
            !res.data.plugins.graph.length
            ) {
              this.initOver = true;
              return;
            }
            const tags = res.data.plugins.graph;
            let hasFileSearchValue = false;
            tags.forEach((k) => {
              this.fileSearchBox.suggestions.push({
                value: k,
              });
              hasFileSearchValue =
              k === this.fileSearchBox.value || hasFileSearchValue;
            });
            if (!this.initOver) {
              this.initOver = true;
              this.fileSearchBox.value = tags.length ? tags[0] : '';
              this.queryGraphData();
            } else if (!hasFileSearchValue) {
              this.fileSearchBox.value = '';
            }
          })
          .catch(() => {
            this.fileSearchBox.suggestions = [];
            this.initOver = true;
            this.loading.show = false;
          });
    },
    /**
     * Close the expanded namespace.
     * @param {String} name The name of the namespace to be closed.
     */
    deleteNamespace(name) {
      this.loading.info = this.$t('graph.searchLoading');
      this.loading.show = true;
      if (!this.selectedNode.more) {
        this.packageDataToObject(name, false);
        this.layoutController(name);
      } else {
        this.allGraphData[name].isUnfold = true;
        this.selectedNode.name = `${name}_unfold`;
        this.layoutNamescope(name, true);
      }
    },
    /**
     * Controls the invoking method of the next step.
     * @param {String} name Name of the namespace to be expanded.
     */
    layoutController(name) {
      if (!this.loading.show) {
        this.loading.info = this.$t('graph.searchLoading');
        this.loading.show = true;
      }
      if (name.includes('/')) {
        const subPath = name.split('/').slice(0, -1).join('/');
        this.layoutNamescope(subPath, true);
      } else {
        const svg = document.querySelector('#graph svg');
        if (svg) {
          svg.remove();
        }
        const dot = this.packageGraphData();
        this.initGraph(dot);
      }
    },
    /**
     * Selecting a node
     * @param {Boolean} needFocus Whether to focus on the node
     */
    selectNode(needFocus = false) {
      window.getSelection().removeAllRanges();
      d3.selectAll(
          '.node polygon, .node ellipse, .node rect, .node path',
      ).classed('selected', false);
      const path = this.selectedNode.name.split('^');
      const node = {};
      let id = path[0].replace('_unfold', '');
      id = this.allGraphData[id].isUnfold ? `${id}_unfold` : id;
      node.eld3 = d3.select(`#graph g[id="${id}"]`);
      node.el = node.eld3.node();
      this.graph.dom.style.transition = '';

      const needDelay = path.length > 1;
      if ((needFocus || needDelay) && node.el) {
        this.selectNodePosition(id, needDelay);
      }
      node.eld3
          .select('polygon, rect, ellipse, path')
          .classed('selected', true);
      this.highlightProxyNodes(id.replace('_unfold', ''));
      this.$refs.tree.setCurrentKey(id.replace('_unfold', ''));
      if (this.isIntoView) {
        this.$nextTick(() => {
          setTimeout(() => {
            const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
            if (dom && this.rightShow) {
              dom.scrollIntoView();
            }
          }, 800);
        });
      }
      this.isIntoView = true;
      this.setNodeData();
    },
    /**
     * The node information of the selected node is displayed and highlighted.
     */
    setNodeData() {
      this.selectedNode.info = {
        input: [],
        inputControl: [],
        output: [],
        outputControl: [],
        attributes: [],
      };
      this.selectedNode.showControl = {
        input: true,
        output: true,
      };

      const path = this.selectedNode.name.split('^');
      const selectedNode = this.allGraphData[path[0].replace('_unfold', '')];

      if (selectedNode && !selectedNode.name.includes('more...')) {
        this.selectedNode.show = true;
        this.selectedNode.name = selectedNode.name;
        this.selectedNode.title = selectedNode.name.replace('_unfold', '');
        this.selectedNode.type =
          selectedNode.type === 'name_scope' ||
          selectedNode.type === 'aggregation_scope'
            ? ''
            : selectedNode.type;
        this.selectedNode.countShow =
          selectedNode.type === 'name_scope' ||
          selectedNode.type === 'aggregation_scope';
        this.selectedNode.count = selectedNode.subnode_count;
        const attrTemp = JSON.parse(JSON.stringify(selectedNode.attr || {}));
        if (attrTemp.shape) {
          const shape = JSON.parse(attrTemp.shape);
          if (shape.length) {
            let str = '';
            for (let i = 0; i < shape.length; i++) {
              str += (str ? ',' : '') + JSON.stringify(shape[i]);
            }
            attrTemp.shape = str;
          }
        }

        this.selectedNode.info.attributes = Object.keys(attrTemp).map((key) => {
          return {
            name: key,
            value: attrTemp[key],
          };
        });

        Object.keys(selectedNode.input).forEach((key) => {
          const value = this.getEdgeLabel(selectedNode.input[key]);
          if (selectedNode.input[key].edge_type !== 'control') {
            this.selectedNode.info.input.push({
              name: key,
              value: value,
            });
          } else {
            this.selectedNode.info.inputControl.push({
              name: key,
              value: value,
            });
          }
        });

        Object.keys(selectedNode.output).forEach((key) => {
          const value = this.getEdgeLabel(selectedNode.output[key]);
          if (selectedNode.output[key].edge_type !== 'control') {
            this.selectedNode.info.output.push({
              name: key,
              scope: selectedNode.output[key].scope,
              value: value,
            });
          } else {
            this.selectedNode.info.outputControl.push({
              name: key,
              scope: selectedNode.output[key].scope,
              value: value,
            });
          }
        });
        this.selectedNode.info.output_i = selectedNode.output_i;
        this.highLightEdges(selectedNode);
      } else {
        this.selectedNode.show = false;
        this.selectedNode.name = '';
        this.selectedNode.title = '';
        this.selectedNode.type = '';
      }
    },
    /**
     * The position is offset to the current node in the center of the screen.
     * @param {String} nodeId Selected Node id
     * @param {Boolean} needDelay Delay required
     */
    selectNodePosition(nodeId, needDelay) {
      const nodeDom = document.querySelector(`#graph0 g[id="${nodeId}"]`);
      const nodeRect = nodeDom.getBoundingClientRect();

      const graph = {};
      graph.rect = this.graph.dom.getBoundingClientRect();
      graph.initWidth = graph.rect.width / this.graph.transform.k;
      graph.initHeight = graph.rect.height / this.graph.transform.k;

      const screenChange = {
        x:
          nodeRect.left +
          nodeRect.width / 2 -
          (this.svg.rect.left + this.svg.rect.width / 2),
        y:
          nodeRect.top +
          nodeRect.height / 2 -
          (this.svg.rect.top + this.svg.rect.height / 2),
      };

      this.graph.transform.x -=
        screenChange.x * (this.svg.originSize.width / graph.initWidth);
      this.graph.transform.y -=
        screenChange.y * (this.svg.originSize.height / graph.initHeight);

      this.graph.dom.setAttribute(
          'transform',
          `translate(${this.graph.transform.x},` +
          `${this.graph.transform.y}) scale(${this.graph.transform.k})`,
      );

      const transitionTime = Math.min(
          Math.abs(screenChange.x) * 2,
          Math.abs(screenChange.y) * 2,
        needDelay ? 800 : 0,
      );

      this.graph.dom.style.transition = `${transitionTime / 1000}s`;
      this.graph.dom.style['transition-timing-function'] = 'linear';

      setTimeout(() => {
        this.graph.dom.style.transition = '';
      }, transitionTime);
      let end = 0;
      this.setInsideBoxData();
      const timer = setInterval(() => {
        this.setInsideBoxData();
        end += 1;
        if (end > transitionTime) {
          clearInterval(timer);
        }
      }, 1);
    },
    /**
     * The drop-down list box of the search drop-down list box is controlled.
     * @param {Object} event Operation event of a component.
     */
    selectBoxVisibleTriggle(event) {
      setTimeout(() => {
        document.querySelector('.el-autocomplete-suggestion').style.display =
          event.type === 'blur' ? 'none' : 'block';
      }, 300);
    },
    /**
     * file select change
     */
    fileChange() {
      this.selectedNode = {
        info: {
          inputControl: [],
          input: [],
          outputControl: [],
          output: [],
          attributes: [],
        },
        showControl: {
          input: true,
          output: true,
        },
      };
      this.clickScope = {};
      this.searchBox.value = '';
      Object.keys(this.allGraphData).forEach((key) => {
        delete this.allGraphData[key];
      });
      d3.select('#graph svg').remove();
      this.firstFloorNodes = [];
      this.queryGraphData();
      this.treeFlag = true;
    },
    /**
     * refresh select list
     * @param {Boolean} expanded Should get data visual plugins or not.
     */
    getSelectList(expanded) {
      if (expanded) {
        this.getDatavisualPlugins();
      }
    },
    /**
     * The search drop-down list box displays the matched data by entering data.
     * @param {String} content Input parameters
     * @param {Object} callback Callback Function
     */
    searchNodesNames() {
      const params = {
        search: this.searchBox.value,
        train_id: this.trainJobID,
        tag: this.fileSearchBox.value,
        offset: 0,
        limit: 1000,
      };
      RequestService.searchNodesNames(params)
          .then(
              (response) => {
                if (response && response.data) {
                  this.treeFlag = false;
                  this.treeWrapFlag = true;
                  this.searchNode.childNodes = [];
                  const data = response.data.nodes.map((val) => {
                    return {
                      label: val.name.split('/').pop(),
                      ...val,
                    };
                  });
                  const currentData = JSON.parse(JSON.stringify(data));
                  currentData.forEach((val) => {
                    val.nodes = [];
                  });
                  this.searchResolve(currentData);
                  data.forEach((val, key) => {
                    if (val.nodes && val.nodes.length) {
                      val.nodes.forEach((value) => {
                        value.parentName = val.name;
                      });
                      this.dealSearchTreeData(val.nodes);
                    }
                  });
                }
              },
              (e) => {
                this.loading.show = false;
              },
          )
          .catch((e) => {
            this.$message.error(this.$t('public.dataError'));
          });
    },
    /**
     * Draw the tree
     * @param {Object} children child node
     */
    dealSearchTreeData(children) {
      children.forEach((val) => {
        const node = this.$refs.searchTree.getNode(val.parentName);
        val.label = val.name.split('/').pop();
        val.leaf =
          val.type === 'name_scope' || val.type === 'aggregation_scope'
            ? false
            : true;
        this.$refs.searchTree.append(val, node);
        node.expanded = true;
        if (val.nodes && val.nodes.length) {
          val.nodes.forEach((value) => {
            value.parentName = val.name;
          });
          this.dealSearchTreeData(val.nodes);
        }
      });
    },
    /**
     * Search for all data of a specific node and its namespace.
     * @param {Object} option Selected node data object
     */
    querySingleNode(option) {
      this.selectedNode.name = option.value;
      this.selectedNode.more = false;
      // If a node exists on the map, select the node.
      if (this.allGraphData[option.value]) {
        if (
          d3
              .select(`g[id="${option.value}"], g[id="${option.value}_unfold"]`)
              .size()
        ) {
          // If the namespace or aggregation node is expanded, you need to close it and select
          if (!this.allGraphData[option.value].isUnfold) {
            this.selectNode(true);
          } else {
            this.dealDoubleClick(option.value);
          }
        } else {
          const parentId = option.value.substring(
              0,
              option.value.lastIndexOf('/'),
          );
          if (
            this.allGraphData[parentId] &&
            this.allGraphData[parentId].isUnfold
          ) {
            const aggregationNode = this.allGraphData[parentId];
            if (aggregationNode && aggregationNode.childIdsList) {
              for (let i = 0; i < aggregationNode.childIdsList.length; i++) {
                if (aggregationNode.childIdsList[i].includes(option.value)) {
                  aggregationNode.index = i;
                  break;
                }
              }
            }
            this.loading.info = this.$t('graph.searchLoading');
            this.loading.show = true;
            this.selectedNode.name = option.value;

            this.$nextTick(() => {
              setTimeout(() => {
                this.layoutNamescope(parentId, true);
              }, 500);
            });
          }
        }
      } else {
        // If the node does not exist and is not a subnode of the aggregation node,
        // directly invoke the background for query.
        // If the node does not exist and is a subnode of the aggregation node, and the aggregation node is not
        // expanded, directly invoke the background to check the node.
        // If the node does not exist and is a child node in the aggregation node,
        // and the aggregation node is expanded but is not displayed on the diagram, you need to zoom out the
        // aggregated node, query the aggregation node again, and intercept the node array again.
        const params = {
          name: option.value,
          train_id: this.trainJobID,
          tag: this.fileSearchBox.value,
        };
        this.loading.info = this.$t('graph.searchLoading');
        this.loading.show = true;
        RequestService.querySingleNode(params)
            .then(
                (response) => {
                  if (response && response.data && response.data.children) {
                    const data = this.findStartUnfoldNode(response.data.children);
                    if (data) {
                      this.dealAutoUnfoldNamescopesData(data);
                    }
                    if (response.data.children) {
                      this.dealTreeData(response.data.children, option.value);
                    }
                  }
                },
                (e) => {
                  this.loading.show = false;
                },
            )
            .catch((e) => {
              this.loading.show = false;
              this.$message.error(this.$t('public.dataError'));
            });
      }
    },
    /**
     * Draw the tree
     * @param {Object} children child node
     * @param {String} name The name of the node that needs to be highlighted
     */
    dealTreeData(children, name) {
      if (children.nodes) {
        if (
          (children.nodes.length > this.nodesCountLimit &&
            this.$refs.tree.getNode(children.scope_name).data.type === 'name_scope') ||
          this.allGraphData[children.scope_name].maxChainNum > this.maxChainNum
        ) {
          return;
        }
        const data = children.nodes.map((val) => {
          return {
            label: val.name.split('/').pop(),
            ...val,
          };
        });
        data.forEach((val) => {
          const node = this.$refs.tree.getNode(children.scope_name);
          if (node.childNodes) {
            if (
              node.childNodes
                  .map((value) => value.data.name)
                  .indexOf(val.name) === -1
            ) {
              this.$refs.tree.append(val, node);
            }
          } else {
            this.$refs.tree.append(val, node);
          }
        });
        const node = this.$refs.tree.getNode(children.scope_name);
        node.childNodes.forEach((val) => {
          if (
            val.data.type !== 'name_scope' &&
            val.data.type !== 'aggregation_scope'
          ) {
            val.isLeaf = true;
          }
        });
        node.expanded = true;
        node.loading = false;
      } else {
        this.$refs.tree.setCurrentKey(name);
        this.$nextTick(() => {
          setTimeout(() => {
            const dom = document.querySelector('.el-tree-node.is-current.is-focusable');
            if (dom && this.rightShow) {
              dom.scrollIntoView();
            }
          }, 800);
        });
      }
      if (children.children && Object.keys(children.children).length) {
        this.dealTreeData(children.children, name);
      }
    },
    /**
     * Processes all data of the queried node and the namespace to which the node belongs.
     * @param {Object} data All data of the node and the namespace to which the node belongs
     * @return {Object} The data object of the namespace to expand.
     */
    dealAutoUnfoldNamescopesData(data) {
      if (!data.scope_name) {
        return this.dealAutoUnfoldNamescopesData(data.children);
      } else {
        if (this.allGraphData[data.scope_name].isUnfold) {
          return this.dealAutoUnfoldNamescopesData(data.children);
        } else {
          // If the namespace is a namespace and the number of subnodes exceeds the upper limit,
          // an error is reported and the namespace is selected.
          if (
            this.allGraphData[data.scope_name].type === 'name_scope' &&
            data.nodes.length > this.nodesCountLimit
          ) {
            this.selectedNode.name = data.scope_name;
            this.querySingleNode({value: data.scope_name});
            this.$message.error(this.$t('graph.tooManyNodes'));
            this.$nextTick(() => {
              this.loading.show = false;
            });
          } else {
            // Normal expansion
            const nodes = JSON.parse(JSON.stringify(data.nodes));
            this.packageDataToObject(data.scope_name, true, nodes);
            if (
              this.allGraphData[data.scope_name].type === 'aggregation_scope'
            ) {
              this.dealAggregationNodes(data.scope_name);
              const aggregationNode = this.allGraphData[data.scope_name];
              if (aggregationNode) {
                for (let i = 0; i < aggregationNode.childIdsList.length; i++) {
                  if (
                    aggregationNode.childIdsList[i].includes(
                        this.selectedNode.name,
                    )
                  ) {
                    aggregationNode.index = i;
                    break;
                  }
                }
              }
              if (
                this.allGraphData[data.scope_name].maxChainNum >
                this.maxChainNum
              ) {
                this.selectedNode.name = data.scope_name;
                this.allGraphData[data.scope_name].isUnfold = false;
                this.deleteNamespace(data.scope_name);
                this.$message.error(this.$t('graph.tooManyChain'));
                this.$nextTick(() => {
                  this.loading.show = false;
                });
                return;
              }
            }

            if (data.children.scope_name) {
              this.dealAutoUnfoldNamescopesData(data.children);
            } else {
              this.loading.info = this.$t('graph.searchLoading');
              this.loading.show = true;
              this.$nextTick(() => {
                setTimeout(() => {
                  this.layoutNamescope(data.scope_name, true);
                }, 200);
              });
            }
          }
        }
      }
    },
    /**
     * Expansion and folding of control edges
     * @param {String} item Determines the control edge of the input or output.
     */
    toggleControl(item) {
      this.selectedNode.showControl[item] = !this.selectedNode.showControl[
          item
      ];
    },
    /**
     * Click the node information name.
     */
    nodeNameClick() {
      const nodeNameText = event.target;
      if (document.body.createTextRange) {
        const nodeNameTextRange = document.body.createTextRange();
        nodeNameTextRange.moveToElementText(nodeNameText);
        nodeNameTextRange.select();
      } else if (window.getSelection) {
        const nodeNameSelection = window.getSelection();
        const nodeNameTextRange = document.createRange();
        nodeNameTextRange.selectNodeContents(nodeNameText);
        nodeNameSelection.removeAllRanges();
        nodeNameSelection.addRange(nodeNameTextRange);
      }
    },
    /**
     * Collapse on the right
     */
    toggleRight() {
      this.rightShow = !this.rightShow;
      setTimeout(() => {
        this.initSvg(false);
        this.initGraphRectData();
      }, 500);
    },
    /**
     * Full-screen display
     */
    toggleScreen() {
      this.fullScreen = !this.fullScreen;
      setTimeout(() => {
        this.initSvg(false);
        this.initGraphRectData();
      }, 500);
    },
    /**
     * Download svg
     */
    downLoadSVG() {
      const svgXml = document.querySelector('#graph #graph0').innerHTML;
      const bbox = document.getElementById('graph0').getBBox();
      const viewBoxSize = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
      const encodeStr =
        `<svg xmlns="http://www.w3.org/2000/svg" ` +
        `xmlns:xlink="http://www.w3.org/1999/xlink" ` +
        `width="${bbox.width}" height="${bbox.height}" ` +
        `viewBox="${viewBoxSize}">${CommonProperty.graphDownloadStyle}<g>${svgXml}</g></svg>`;

      const downloadLink = document.createElement('a');
      downloadLink.download = 'graph.svg';
      downloadLink.style.display = 'none';
      const blob = new Blob([encodeStr], {type: 'text/html'});
      downloadLink.href = URL.createObjectURL(blob);
      document.body.appendChild(downloadLink);
      downloadLink.click();
      document.body.removeChild(downloadLink);
    },
    /**
     * Fold the legend area.
     */
    foldLegend() {
      this.showLegend = !this.showLegend;
    },
    /**
     * Control Display User Guide
     */
    showUserGuide() {
      this.guide.content = [
        this.$t('graph.guideContent11'),
        this.$t('graph.guideContent12'),
        this.$t('graph.guideContent13'),
        this.$t('graph.guideContent14'),
      ];
      this.guide.show = true;
      this.guide.step = 1;
    },
    /**
     * Close user guide
     */
    closeUserGuide() {
      this.guide.show = false;
    },
    /**
     * Show the next step
     */
    guideNext() {
      if (this.guide.step < 3) {
        this.guide.step++;
        switch (this.guide.step) {
          case 2:
            this.guide.content = [this.$t('graph.guideContent2')];
            break;
          case 3:
            this.guide.content = [this.$t('graph.guideContent3')];
            break;
          default:
            break;
        }
      } else if (this.guide.step >= 3) {
        this.guide.show = false;
      }
    },
    /**
     * jump back to train dashboard
     */
    jumpToTrainDashboard() {
      this.$router.push({
        path: '/train-manage/training-dashboard',
        query: {
          id: this.trainJobID,
        },
      });
    },
  },
  // Components imported by the page
  components: {},
};
</script>
<style>
.tooltip-container .cl-graph-sidebar-tip {
  word-break: normal;
}

.cl-graph-manage {
  height: 100%;
}
.cl-graph-manage .cl-graph-title {
  height: 50px;
  line-height: 50px;
}
.cl-graph-manage .cl-graph-title .path-message {
  display: inline-block;
  line-height: 20px;
  padding: 0px 4px 15px 4px;
  font-weight: bold;
  vertical-align: bottom;
}
.cl-graph-manage .cl-graph-title .guide {
  cursor: pointer;
  margin-left: 10px;
  display: inline-block;
  line-height: 18px;
  font-size: 12px;
}
.cl-graph-manage .cl-graph-title .guide .guide-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: -2.5px;
  margin-right: 4px;
  background: url("../../assets/images/guideIcon.svg");
}
.cl-graph-manage .cl-graph-title .guide:hover {
  color: #00a5a7;
}
.cl-graph-manage .cl-graph-title .guide:hover .guide-icon {
  background: url("../../assets/images/guideIconHover.svg");
}
.cl-graph-manage .graph-p32 {
  height: 100%;
  position: relative;
}
.cl-graph-manage .graph-p32 .guide-content {
  height: 100%;
  width: 100%;
  position: absolute;
  background-color: #c6c8cc;
  z-index: 9999;
}
.cl-graph-manage .graph-p32 .guide-content .step-pic {
  text-align: center;
  margin-top: 8px;
}
.cl-graph-manage .graph-p32 .guide-content .step {
  height: 100%;
  background-repeat: round;
  user-select: none;
}
.cl-graph-manage .graph-p32 .guide-content .step img {
  width: 100%;
}
.cl-graph-manage .graph-p32 .guide-content .guide-span {
  font-size: 12px;
  color: #575d6c;
  line-height: 18px;
  text-align: left;
  display: inline-block;
}
.cl-graph-manage .graph-p32 .guide-content .el-popover .el-icon-close {
  cursor: pointer;
  position: absolute;
  right: 10px;
  top: 13px;
  font-size: 20px;
}
.cl-graph-manage .graph-p32 .guide-content .el-popover .el-icon-close:hover {
  color: #00a5a7;
}
.cl-graph-manage .graph-p32 .guide-content .el-popover__title {
  font-size: 16px;
  color: #252b3a;
  line-height: 24px;
  font-weight: bold;
}
.cl-graph-manage .graph-p32 .guide-content .el-button {
  display: block;
  float: right;
  height: 28px;
  line-height: 27px;
  border-radius: 0;
  padding: 0 20px;
}
.cl-graph-manage .cl-content {
  height: calc(100% - 50px);
  overflow: auto;
}
.cl-graph-manage #graphs {
  width: 100%;
  height: 100%;
  font-size: 0;
  background: #f0f2f5;
}
.cl-graph-manage #graphs .search {
  margin-bottom: 15px;
  width: 100%;
}
.cl-graph-manage #graphs .search-wrap {
  position: relative;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap {
  position: absolute;
  left: 0;
  top: 32px;
  z-index: 101;
  width: 100%;
  max-height: 224px;
  overflow: auto;
  border: 1px solid #dcdfe6;
  border-top: none;
  background: #fff;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap .image-type {
  width: 20px;
  height: 10px;
  margin-right: 10px;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap .el-tree > .el-tree-node {
  min-width: 100%;
  display: inline-block;
}
.cl-graph-manage #graphs .search-wrap .tree-wrap .el-tree .custom-tree-node {
  padding-right: 8px;
}
.cl-graph-manage #graphs .search-wrap .collapse_i {
  cursor: pointer;
}
.cl-graph-manage #graphs .cl-graph {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #fff;
  padding: 0 32px 10px;
  min-height: 700px;
  overflow: hidden;
}
.cl-graph-manage #graphs .cl-graph.full-screen {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -280px;
  right: 0;
  width: auto;
  height: auto;
  padding: 0;
}
.cl-graph-manage #graphs .cl-graph.full-screen #sidebar .node-info-con {
  height: calc(100% - 300px);
}
.cl-graph-manage #graphs .cl-graph.full-screen .graph-container {
  width: 100%;
}
.cl-graph-manage #graphs #sidebar.right-hide {
  right: -442px;
}
.cl-graph-manage #graphs #sidebar {
  position: absolute;
  right: 0;
  top: 0;
  width: 442px;
  height: 100%;
  border-radius: 6px;
  text-align: left;
  background-color: #ffffff;
  display: inline-block;
  box-shadow: 0 1px 3px 0px rgba(0, 0, 0, 0.1);
  color: #333333;
  font-size: 14px;
  line-height: 14px;
  padding: 18px 32px 10px;
}
.cl-graph-manage #graphs #sidebar .sidebar-tooltip {
  position: absolute;
  height: 32px;
  top: 18px;
  left: 10px;
  display: flex;
  align-items: center;
  font-size: 16px;
  color: #6c7280;
}
.cl-graph-manage #graphs #sidebar div,
.cl-graph-manage #graphs #sidebar span,
.cl-graph-manage #graphs #sidebar pre {
  font-size: 14px;
}
.cl-graph-manage #graphs #sidebar #small-container {
  height: 209px;
  width: 100%;
  z-index: 100;
  border: 1px solid #e6ebf5;
  overflow: hidden;
  background-color: white;
  position: relative;
}
.cl-graph-manage #graphs #sidebar #small-container #small-resize {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
}
.cl-graph-manage #graphs #sidebar #small-container #small-map {
  height: 100%;
  width: 100%;
  position: relative;
  padding: 0;
}
.cl-graph-manage #graphs #sidebar #small-container #inside-box {
  background-color: #5b88f1;
  position: absolute;
  /* Transparency */
  opacity: 0.3;
  width: 100%;
  height: 100%;
  left: 0px;
  top: 0px;
  z-index: 200;
  cursor: move;
}
.cl-graph-manage #graphs #sidebar .title {
  padding: 20px 0;
  font-size: 14px;
  color: #333333;
}
.cl-graph-manage #graphs #sidebar .title img {
  float: right;
  margin-right: 10px;
  cursor: pointer;
}
.cl-graph-manage #graphs #sidebar .graph-controls {
  padding: 10px 20px 0 20px;
}
.cl-graph-manage #graphs #sidebar .graph-controls div {
  cursor: pointer;
  display: inline-block;
  margin-left: 20px;
}
.cl-graph-manage #graphs #sidebar .graph-controls img {
  cursor: pointer;
  vertical-align: middle;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button {
  z-index: 200;
  width: 10px;
  height: 10px;
  background: #fff;
  cursor: pointer;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:horizontal:single-button:start {
  background-image: url("../../assets/images/scroll-btn-left.png");
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:horizontal:single-button:end {
  background-image: url("../../assets/images/scroll-btn-right.png");
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:vertical:single-button:start {
  background-image: url("../../assets/images/scroll-btn-up.png");
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-button:vertical:single-button:end {
  background-image: url("../../assets/images/scroll-btn-down.png");
  background-position: center;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar-thumb {
  background-color: #bac5cc;
}
.cl-graph-manage #graphs #sidebar .node-info-con ::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.cl-graph-manage #graphs #sidebar .node-info-container {
  height: calc(100% - 451px);
}
.cl-graph-manage #graphs #sidebar .node-info-container-long {
  height: calc(100% - 357px);
}
.cl-graph-manage #graphs #sidebar .node-info {
  font-size: 14px;
  padding: 0 20px;
  height: calc(100% - 54px);
  overflow: auto;
  color: #333;
  background-color: #f7faff;
}
.cl-graph-manage #graphs #sidebar .node-info .clear {
  clear: both;
}
.cl-graph-manage #graphs #sidebar .node-info .hover li:hover {
  background: #fce8b2;
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list .dependence-title {
  line-height: 30px;
  cursor: pointer;
  font-weight: bold;
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list .dependence-title img {
  vertical-align: middle;
  margin-right: 3px;
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list .dependence-title.hide img {
  margin-top: -3px;
  transform: rotate(-90deg);
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list li:hover {
  background: #fce8b2;
}
.cl-graph-manage #graphs #sidebar .node-info .hover .control-list:hover {
  background: none;
}
.cl-graph-manage #graphs #sidebar .node-info .pointer {
  cursor: pointer;
}
.cl-graph-manage #graphs #sidebar .node-info .item-content {
  max-height: calc(50% - 95px);
  overflow: auto;
}
.cl-graph-manage #graphs #sidebar .node-info .item-content li {
  min-width: 100%;
  width: max-content;
}
.cl-graph-manage #graphs #sidebar .node-info .item-min {
  min-height: 50px;
}
.cl-graph-manage #graphs #sidebar .node-info .item-min2 {
  min-height: 87px;
}
.cl-graph-manage #graphs #sidebar .node-info .items {
  line-height: 20px;
  padding: 9px 0;
}
.cl-graph-manage #graphs #sidebar .node-info .items .items-over {
  max-height: 60px;
  overflow: auto;
}
.cl-graph-manage #graphs #sidebar .node-info .items .item {
  color: #999;
}
.cl-graph-manage #graphs #sidebar .node-info .shape {
  vertical-align: top;
  width: 50px;
  word-break: break-all;
  display: inline-table;
  position: absolute;
  left: 0;
}
.cl-graph-manage #graphs #sidebar .node-info .key {
  vertical-align: top;
  width: 60px;
  word-break: break-all;
  display: inline-table;
}
.cl-graph-manage #graphs #sidebar .node-info .label {
  vertical-align: top;
  width: 70px;
  word-break: break-all;
  display: inline-block;
}
.cl-graph-manage #graphs #sidebar .node-info .value {
  vertical-align: top;
  display: inline-block;
  width: calc(100% - 70px);
  white-space: nowrap;
  overflow: auto;
}
.cl-graph-manage #graphs #sidebar .node-info .size {
  width: 310px;
  font-size: 12px;
  text-align: right;
}
.cl-graph-manage #graphs #sidebar .node-info .input {
  width: 100%;
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.cl-graph-manage #graphs #sidebar .node-info ul li {
  line-height: 20px;
}
.cl-graph-manage #graphs #sidebar .legend .legend-content {
  background-color: #f7faff;
  padding: 0 32px;
  height: 94px;
  overflow-y: auto;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item {
  padding: 5px 0;
  display: inline-block;
  width: 50%;
  font-size: 14px;
  line-height: 20px;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .pic {
  width: 45px;
  text-align: center;
  display: inline-block;
  padding-left: 20px;
  vertical-align: middle;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .pic img {
  max-width: 45px;
  max-height: 15px;
  margin-left: -20px;
  vertical-align: middle;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .legend-text {
  display: inline-block;
  padding-left: 20px;
  width: calc(100% - 45px);
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cl-graph-manage #graphs #sidebar .legend .legend-item .legend-text:hover {
  cursor: default;
}
.cl-graph-manage #graphs #sidebar .toggle-right {
  position: absolute;
  top: calc(50% - 43px);
  left: -16px;
  width: 18px;
  height: 86px;
  cursor: pointer;
  background-image: url("../../assets/images/toggle-right-bg.png");
}
.cl-graph-manage #graphs #sidebar .icon-toggle {
  width: 6px;
  height: 9px;
  background-image: url("../../assets/images/toggle-right-icon.png");
  position: absolute;
  top: calc(50% - 4.5px);
  left: calc(50% - 3px);
}
.cl-graph-manage #graphs #sidebar .icon-toggle.icon-left {
  transform: rotateY(180deg);
}
.cl-graph-manage #graphs .operate-button-list {
  position: absolute;
  right: 0;
  top: 0;
  z-index: 100;
}
.cl-graph-manage #graphs .operate-button-list div {
  cursor: pointer;
  width: 12px;
  height: 12px;
  display: inline-block;
  margin: 5px;
}
.cl-graph-manage #graphs .operate-button-list .download-button {
  background-image: url("../../assets/images/download.png");
}
.cl-graph-manage #graphs .operate-button-list .full-screen-button {
  background-image: url("../../assets/images/full-screen.png");
}
.cl-graph-manage #graphs .graph-container.all {
  width: 100%;
}
.cl-graph-manage #graphs .graph-container .node:hover > path,
.cl-graph-manage #graphs .graph-container .node:hover > ellipse,
.cl-graph-manage #graphs .graph-container .node:hover > polygon,
.cl-graph-manage #graphs .graph-container .node:hover > rect {
  stroke-width: 2px;
}
.cl-graph-manage #graphs .graph-container .node.cluster > rect:hover {
  stroke: #8df1f2;
}
.cl-graph-manage #graphs .graph-container .selected {
  stroke: red !important;
  stroke-width: 2px;
}
.cl-graph-manage #graphs .graph-container,
.cl-graph-manage #graphs #small-map {
  font-size: 16px;
  position: relative;
  display: inline-block;
  width: calc(100% - 442px);
  height: calc(100% - 5px);
  text-align: left;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.cl-graph-manage #graphs .graph-container .graph,
.cl-graph-manage #graphs #small-map .graph {
  height: 100%;
  background-color: #f7faff;
}
.cl-graph-manage #graphs .graph-container #graph0 > polygon,
.cl-graph-manage #graphs #small-map #graph0 > polygon {
  fill: transparent;
}
.cl-graph-manage #graphs .graph-container .node,
.cl-graph-manage #graphs #small-map .node {
  cursor: pointer;
}
.cl-graph-manage #graphs .graph-container .edge path,
.cl-graph-manage #graphs #small-map .edge path {
  stroke: #787878;
}
.cl-graph-manage #graphs .graph-container .edge polygon,
.cl-graph-manage #graphs #small-map .edge polygon {
  fill: #787878;
}
.cl-graph-manage #graphs .graph-container .edge.highlighted path,
.cl-graph-manage #graphs #small-map .edge.highlighted path {
  stroke: red;
}
.cl-graph-manage #graphs .graph-container .edge.highlighted polygon,
.cl-graph-manage #graphs #small-map .edge.highlighted polygon {
  stroke: red;
  fill: red;
}
.cl-graph-manage #graphs .graph-container .edge.highlighted marker path,
.cl-graph-manage #graphs #small-map .edge.highlighted marker path {
  fill: red;
}
.cl-graph-manage #graphs .graph-container .node.aggregation > polygon,
.cl-graph-manage #graphs #small-map .node.aggregation > polygon {
  stroke: #e3aa00;
  fill: #ffe794;
}
.cl-graph-manage #graphs .graph-container .node.cluster.aggregation > rect,
.cl-graph-manage #graphs #small-map .node.cluster.aggregation > rect {
  stroke: #e3aa00;
  fill: #ffe794;
  stroke-dasharray: 3, 3;
}
.cl-graph-manage #graphs .graph-container .node > polygon,
.cl-graph-manage #graphs #small-map .node > polygon {
  stroke: #00a5a7;
  fill: #8df1f2;
}
.cl-graph-manage #graphs .graph-container .node > ellipse,
.cl-graph-manage #graphs #small-map .node > ellipse {
  stroke: #4ea6e6;
  fill: #b8e0ff;
}
.cl-graph-manage #graphs .graph-container .plain > path,
.cl-graph-manage #graphs .graph-container .plain ellipse,
.cl-graph-manage #graphs #small-map .plain > path,
.cl-graph-manage #graphs #small-map .plain ellipse {
  stroke: #e37d29;
  fill: #ffd0a6;
  stroke-dasharray: 1.5, 1.5;
}
.cl-graph-manage #graphs .graph-container .edge-point ellipse,
.cl-graph-manage #graphs #small-map .edge-point ellipse {
  stroke: #a7a7a7;
  fill: #a7a7a7;
}
.cl-graph-manage #graphs .graph-container text,
.cl-graph-manage #graphs #small-map text {
  fill: black;
}
.cl-graph-manage #graphs .image-noData {
  width: 100%;
  height: 100%;
  background: #fff;
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  z-index: 200;
}
.cl-graph-manage #graphs .noData-text {
  margin-top: 33px;
  font-size: 18px;
}
.cl-graph-manage .cl-display-block {
  display: block;
}
.cl-graph-manage .cl-input-value {
  width: calc(100% - 70px) !important;
  margin-left: 10px !important;
}
.cl-graph-manage .cl-close-btn {
  width: 20px;
  height: 20px;
  vertical-align: -3px;
  cursor: pointer;
  display: inline-block;
}
.cl-graph-manage .cl-title-right {
  padding-right: 32px;
}

#graphTemp,
#subgraphTemp {
  position: absolute;
  bottom: 0;
  visibility: hidden;
}
</style>
