Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
antvis avatar

Antv X6 Editor

  • 327 installs
  • 454 repo stars
  • Updated July 31, 2026
  • antvis/chart-visualization-skills

Embed AntV X6 diagram editors for flowcharts, workflows, and node-based UIs with drag-drop, connectors, snapping, and serialization in web apps.

About

Guides implementation of AntV X6 diagram and flow editors in web frontends, covering custom nodes, connectors, drag-and-drop tooling, routing, and persistence for workflow or visual builder products.

  • X6 canvas and stencil setup
  • Custom shapes and ports
  • Drag-drop and snap-to-grid
  • Connector and edge routing rules
  • Serialize and restore diagram state

Antv X6 Editor by the numbers

  • 327 all-time installs (skills.sh)
  • Ranked #722 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/antvis/chart-visualization-skills --skill antv-x6-editor

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs327
repo stars454
Last updatedJuly 31, 2026
Repositoryantvis/chart-visualization-skills

What it does

Embed AntV X6 diagram editors for flowcharts, workflows, and node-based UIs with drag-drop, connectors, snapping, and serialization in web apps.

Files

SKILL.mdMarkdownGitHub ↗

X6 图编辑引擎代码生成技能

核心约束(必须遵守)

<!-- CONSTRAINTS:START -->

X6 3.x 关键约束(强制)

  • `graph.render()` 不存在:X6 3.x 中 new Graph() / addNode / addEdge / fromJSON 均自动渲染,代码中不得出现 graph.render()
  • 不得声明 `container` 变量:运行环境会作为函数参数注入 container。Graph 初始化只使用字符串字面量 container: 'container',禁止 const/let/var container = ...,也禁止 document.getElementById('container')
  • 使用插件方法前必须先 `graph.use(new Plugin(...))` 注册对应插件graph.toPNG / toSVG / toJPEG 依赖 Exportgraph.select / unselect 依赖 Selectiongraph.undo / redo 依赖 Historygraph.copy / paste / cut 依赖 Clipboardgraph.bindKey 依赖 Keyboard。未注册插件时对应方法不存在。
  • 自定义 shape 必须先注册再使用Graph.registerNode(name, def) / Graph.registerEdge(name, def) / Shape.HTML.register({ shape, ... }) 必须在首次 addNode / addEdge 之前完成。
  • `@antv/x6` 仅导出 11 个插件类ClipboardDndExportHistoryKeyboardMiniMapScrollerSelectionSnaplineStencilTransformmousewheelembeddingpanningconnectingtranslatinginteractingbackgroundgridnew Graph()构造选项,不是插件,不得 import 同名类、不得 graph.use(new XxxClass())。例:滚轮缩放写在 Graph 构造中 mousewheel: { enabled: true, zoomAtMousePosition: true, modifiers: ['ctrl'] }
  • 节点/边动画使用 `cell.animate(keyframes, options)`(Web Animations API 风格):X6 3.x 没有 `node.transition(path, target, options)` 方法。源码中 transition 仅作为 node.translate(tx, ty, { transition }) / node.rotate(deg, { transition })options 字段存在(boolean | KeyframeEffectOptions),并非独立方法。示例:
  // 通用动画
  node.animate(
    { fill: ['#fff', '#1890ff'], transform: ['scale(1)', 'scale(1.2)'] },
    { duration: 500, iterations: 1, fill: 'forwards' },
  );
  // 仅平移过渡
  node.translate(120, 0, { transition: { duration: 500, easing: 'ease-in-out' } });

多步属性变更可用 graph.startBatch('animate'); cell.attr(...); graph.stopBatch('animate'); 包装。

初始化规范

  • container 参数必填,必须使用字符串形式 container: 'container',运行时环境会自动解析为 DOM 元素
  • 必须设置背景色background: { color: '#F2F7FA' },所有画布都需要统一的浅蓝灰色背景
  • 不要添加 `grid` 配置,除非用户明确要求显示网格
  • 不要设置 `width` / `height`,除非用户明确指定画布尺寸;画布默认自适应容器大小
  • 导入方式:import { Graph } from '@antv/x6'仅导入实际使用到的类
  • 禁止无条件导入 Shape:仅在使用 Shape.HTML.register() 等 Shape 静态方法时才导入 Shape
  • 插件从 '@antv/x6' 直接导入,如 import { Graph, Selection, History } from '@antv/x6'
  • 禁止使用 @antv/x6-plugin-xxx 独立包导入(已废弃)
  • 标准初始化模板:
  import { Graph } from '@antv/x6';
  const graph = new Graph({
    container: 'container',
    background: { color: '#F2F7FA' },
  });

节点操作规范

  • 优先使用 `graph.addNode()` 逐个添加节点,而非 graph.fromJSON() 批量导入(除非用户明确要求批量加载数据)
  • 内置 shape:'rect''circle''ellipse''polygon''polyline''path''text''text-block''image''html'
  • 节点样式通过 attrs 配置,遵循 SVG 属性命名
  • 节点位置通过 xy 设置(左上角坐标),尺寸通过 widthheight 设置
  • 默认节点样式(除非用户指定其他样式,所有节点统一使用此默认样式):
  attrs: {
    body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 },
  }
  • 禁止在 attrs 中使用 CSS 属性名(如 background-color),必须用 SVG 属性(如 fill

边操作规范

  • 使用 graph.addEdge({ source, target, ... }) 添加边
  • source/target 可以是:节点实例、节点 ID 字符串、{ cell: node, port: 'portId' } 对象、坐标 { x, y }
  • 边样式:attrs: { line: { stroke, strokeWidth, strokeDasharray, targetMarker, sourceMarker } }
  • 默认边样式(除非用户指定其他样式):attrs: { line: { stroke: '#8f8f8f', strokeWidth: 1 } }
  • 箭头:targetMarker: 'classic'(经典箭头)、'block''circle''diamond'
  • 路由器:router: 'orth'(正交)、'manhattan''metro''er'
  • 连接器:connector: 'rounded'(圆角)、'smooth'(贝塞尔曲线)、'jumpover'

连接桩(Ports)规范

  • 连接桩定义在节点配置的 ports 字段中
  • 端口组:ports: { groups: { groupName: { position, attrs, ... } }, items: [{ id, group }] }
  • position 取值:'top''bottom''left''right'
  • 端口是连线的锚点,设置 attrs: { circle: { magnet: true } } 允许从端口拖出连线
  • 必须设置 `magnet: true` 才能从该端口发起或接收连线

交互配置规范

  • 连线交互在 Graph 配置中通过 connecting 字段设置
  • connecting: { allowBlank: false, router: 'orth', connector: 'rounded', createEdge() {...} }
  • 节点移动限制:translating: { restrict: true } 或传函数限制区域
  • 嵌入:embedding: { enabled: true } 允许节点拖入分组

插件使用规范

  • 插件从 @antv/x6 导入,通过 graph.use(new Plugin(options)) 注册
  • 可用插件:SelectionSnaplineHistoryClipboardKeyboardScrollerMiniMapTransformExportStencilDnd
  • Selection: graph.use(new Selection({ enabled: true, rubberband: true }))
  • Snapline: graph.use(new Snapline({ enabled: true }))
  • History: graph.use(new History({ enabled: true }))
  • Clipboard: graph.use(new Clipboard({ enabled: true }))
  • Keyboard: graph.use(new Keyboard({ enabled: true }))
  • Scroller: graph.use(new Scroller({ enabled: true }))
  • MiniMap: graph.use(new MiniMap({ enabled: true, container: minimapContainer }))
  • Transform: graph.use(new Transform({ resizing: { enabled: true }, rotating: { enabled: true } }))
  • Export: graph.use(new Export())(注册后可调用 graph.toPNG() / graph.toSVG()
  • 动态控制:graph.enablePlugins('selection') / graph.disablePlugins('selection')
  • 禁止在 Graph 构造函数中直接传入 selectingsnapline 等选项(3.x 不支持)

序列化规范

  • 导出:const data = graph.toJSON() 返回 { cells: [...] } 对象
  • 导入:graph.fromJSON(data) 加载整个图数据
  • 清空:graph.clearCells() 清除所有元素
  • 禁止手动构造 cells 数组中的内部字段(如 zIndexparent),应通过 API 操作

事件规范

  • 节点事件:graph.on('node:click', ({ node, e }) => {...})
  • 边事件:graph.on('edge:click', ({ edge, e }) => {...})
  • 画布事件:graph.on('blank:click', ({ e }) => {...})
  • 变更事件:graph.on('node:moved', ({ node }) => {...})
  • 事件回调参数是对象,不是位置参数:({ node, e }) 而非 (node, e)

导入规范

  • 所有使用到的类都必须出现在 import 语句中:如使用 Selection,必须 import { Graph, Selection } from '@antv/x6'
  • 禁止使用 Graph.SelectionGraph.Keyboard 等命名空间写法(不存在)
  • 禁止使用未导入的类:new Selection(...) 必须对应 import { Selection } from '@antv/x6'
  • import 自检清单(强制,输出代码前必须逐行核对):对代码中每一个 new XxxYyy(...) 调用,XxxYyy 必须字面出现在第一行 import { ..., XxxYyy } from '@antv/x6' 的花括号内。常见漏写:SelectionKeyboardHistoryClipboardSnaplineMiniMapTransformScrollerExportStencilDndShape(用到 Shape.HTML.register 时)。
  • import 漏写为何会变成 `Illegal constructor` 等运行时错:评测/Playground 环境用 UMD 构建window.X6)执行代码,而不是真正的 ES Module。SelectionKeyboard 等会根据 import 列表window.X6 解构出来;如果 import 漏写,标识符 Selection回退到 `window.Selection`(浏览器原生 Selection 接口),new Selection({...}) 会抛 Failed to construct 'Selection': Illegal constructorKeyboard / History 等同理(会 is not a constructor)。
  • import 漏写的标准修法:把所有用到的插件类合并到同一行 import { Graph, Selection, Keyboard, History, ... } from '@antv/x6';,不要分多行 import,也不要遗漏。

节点工具(Tools)规范

  • 添加工具:node.addTools([{ name: 'button-remove', args: { x: 0, y: 0 } }])graph.addTools(node, [...])
  • 移除工具:node.removeTools()graph.removeTools(node)
  • 检查工具:node.hasTools()
  • 禁止使用 node.hideTools() / node.showTools()(3.x 不存在此 API)
  • 悬停显示/隐藏工具的正确方式:
  graph.on('node:mouseenter', ({ node }) => {
    node.addTools([{ name: 'button-remove', args: { x: 0, y: 0 } }]);
  });
  graph.on('node:mouseleave', ({ node }) => {
    node.removeTools();
  });

渐变色规范

  • X6 attrs 中 fill 支持渐变对象语法,禁止直接操作 graph.defsdocument.createElementNS 创建 SVG 渐变
  • 线性渐变正确写法:
  attrs: {
    body: {
      fill: {
        type: 'linearGradient',
        stops: [
          { offset: '0%', color: '#0000ff' },
          { offset: '100%', color: '#00ff00' },
        ],
      },
    },
  }

代码输出规范

  • 必须输出纯 JavaScript,禁止使用 TypeScript 语法(如 private、类型注解 : stringas 类型断言)
  • HTML 自定义节点使用 Shape.HTML.register({ shape, html, effect }) 注册自定义 shape,禁止使用 class extends Node 方式
  • 禁止 Graph.registerHTMLComponent(name, factory) —— 这是 X6 2.x 旧 API,3.x 源码已无此方法。所有 HTML 节点统一通过 Shape.HTML.register 注册(详见 references/core/x6-core-html-shape.md
  • effect 数组指定哪些属性变化时触发重新渲染(如 ['data']);纯静态展示节点不要加 effect
  • HTML 节点正确写法:
  import { Graph, Shape } from '@antv/x6';
  Shape.HTML.register({
    shape: 'my-html',
    effect: ['data'],
    html(node) {
      const div = document.createElement('div');
      div.style.width = '100%';
      div.style.height = '100%';
      div.innerHTML = node.getData().content || '';
      return div;
    },
  });
  const graph = new Graph({ container: 'container' });
  graph.addNode({ shape: 'my-html', x: 100, y: 100, width: 200, height: 80, data: { content: '<div>Hello</div>' } });

Stencil 插件规范

  • Stencil 通过 graph.use(new Stencil({ target: graph, groups: [...] })) 注册
  • 注册后通过 graph.getPlugin('stencil') 获取实例,将 stencil.container 挂载到 DOM
  • Stencil 内的节点模板使用 graph.createNode(...) 创建(非 graph.addNode),再通过 stencil.load(nodes, groupName) 加载

动态端口规范

  • 使用 node.addPort() 动态添加端口时,必须在节点初始化时预定义对应的 ports.groups
  • 如果没有预定义 group,端口无法正确定位,可能导致渲染异常
  • 正确写法:
  const node = graph.addNode({
    ...,
    ports: {
      groups: {
        in: { position: 'left', attrs: { circle: { r: 4, magnet: true, stroke: '#8f8f8f', fill: '#fff' } } },
        out: { position: 'right', attrs: { circle: { r: 4, magnet: true, stroke: '#8f8f8f', fill: '#fff' } } },
      },
    },
  });
  node.addPort({ id: 'port1', group: 'out' });
  • `registerNode` + `addNode` 的 ports 合并陷阱(强约束)Graph.registerNode(name, { ports: { items: [{ id: 'in1', group: 'in' }] } }) 之后,如果再 graph.addNode({ shape: name, ports: { items: [{ id: 'in1', group: 'in' }] } }),X6 内部 Cell 构造时 ObjectExt.merge(defaults, metadata) 会按数组下标合并、node.addPorts 走的也是 [...current, ...new] 简单拼接,不会去重,运行时直接抛 Error: Duplicitied port id.。正确做法二选一:
  • registerNode 里只声明 ports.groups,把 ports.items 留给 addNode / 后续 node.addPort 提供;
  • 或在 registerNode 里完整声明 ports.itemsaddNode不再传 `ports.items`(如需追加端口,调用 node.addPort({ id: '新id', group: 'xxx' }),且新 id 不能与 registry 里已声明的重名)。

DOM/CSS 操作规范(HTML 节点 / Stencil / 自定义工具)

  • HTML 节点 html(node) 回调里给 DOM 设样式时,禁止直接写连字符属性:el.style.box-sizing = '...'el.style.font-size = '...' 会被 JS 解析为 el.style.box - sizing = ...,抛 Invalid left-hand side in assignment。正确写法二选一:
  • 驼峰:el.style.boxSizing = 'border-box'el.style.fontSize = '14px'el.style.backgroundColor = '#fff'
  • 方括号:el.style['box-sizing'] = 'border-box'el.style['font-size'] = '14px'
  • 多条样式优先用 el.style.cssText = 'box-sizing:border-box;font-size:14px;'Object.assign(el.style, { boxSizing: 'border-box', fontSize: '14px' })
  • 同理,el.classList.add('...') / el.setAttribute('data-x', '...') 是合法 API;禁止 el.class = '...' / el['class-name'] = ...

不存在的 API(禁止使用)

  • 禁止 graph.scrollToCell() → 正确方式:graph.centerCell(cell) 滚动并居中到指定 cell
  • 禁止 graph.highlightCell() / graph.highlightNode() → 正确方式:通过 node.attr('body/stroke', '#f00') 或添加 CSS class 实现高亮
  • 禁止 Shape.Cylinder / Shape.Diamond 等不存在的内置 Shape → 用 'rect' + rx/ry'polygon' 自定义
  • 禁止 Shape.Edge.define() / Shape.Node.define() → 正确方式:Graph.registerEdge() / Graph.registerNode()
  • 禁止 Shape.Group / Shape.Group.define() / new Shape.Group() → X6 3.x 的 Shape 命名空间没有 Group 导出(实际只有 Circle / Edge / Ellipse / HTML / Image / Path / Polygon / Polyline / Rect / TextBlock,运行时会报 Cannot read properties of undefined (reading 'define'))。父子分组的正确方式:直接 graph.addNode({ shape: 'rect', ... }) 创建一个普通节点作为父节点,再通过 parent.addChild(child) / parent.embed(child) 建立父子关系;或用 Graph.registerNode('my-group', { inherit: 'rect', markup: [...], attrs: {...} }) 注册一个自定义分组形状再 addNode({ shape: 'my-group' })
  • 禁止Embedding 当插件 import / new / graph.use(new Embedding(...)) → X6 3.x 没有 Embedding 插件类(运行时会报 Embedding is not a constructor)。节点嵌入是 Graph 构造选项new Graph({ container, embedding: { enabled: true, findParent: 'bbox', frontOnly: false, validate: ({ child, parent }) => true } })。Hover 高亮通过 highlighting.embedding 配置,嵌入/解除嵌入事件为 node:embedding / node:embedded
  • 禁止 history.batch() → 正确方式:graph.startBatch('custom'); ...; graph.stopBatch('custom');graph.batchUpdate(() => { ... })
  • 禁止 graph.defs / graph.svgDoc / document.createElementNS('...', 'linearGradient' | 'defs' | 'marker') → X6 3.x 不暴露 graph.defs / graph.svgDoc,运行时会报 Cannot read properties of undefined。正确方式:
  • 节点/边普通 fill 渐变:直接用 attrs 中的渐变对象语法(fill: { type: 'linearGradient', stops: [...] }
  • 自定义 marker 需要渐变填充:先 const id = graph.defineGradient({ type: 'linearGradient', stops: [{ offset: 0, color: '#f00' }, { offset: 1, color: '#0f0' }] }),再在 marker 对象里写 fill: \url(#${id})\``
  • 自定义 marker / filter 同理使用 graph.defineMarker(options) / graph.defineFilter(options)

渲染输出规范(必须遵守)

  • 画布初始化后必须存在至少一个 `graph.addNode` / `graph.addEdge` / `graph.fromJSON` 调用,确保画布有可视内容。即使用户 query 只描述了交互(panning / mousewheel / 插件等)配置,也必须自行补 2~3 个示例节点 + 1 条边作为渲染载体,否则视觉验证会被判定为「白屏」。
  • 所有节点/边添加完成后,必须在末尾调用 `graph.centerContent()`(或在画布需要随内容缩放时使用 graph.zoomToFit({ padding: 20, maxScale: 1 }))。X6 默认不会自动居中,缺失该调用会导致内容偏向左上角、视觉评分不通过。两者二选一,不可同时调用。
  • 多个交互(panning + mousewheel + Selection rubberband)同时启用时,必须用 `modifiers` 错开触发条件(例如:panning 用 'shift',mousewheel 用 'ctrl',rubberband 留空)。禁止'mouseWheel' 放进 panning.eventTypes 同时又启用 mousewheel,两者会争抢滚轮事件。

<!-- CONSTRAINTS:END -->

---

禁止的错误模式

❌ 使用已废弃的独立插件包

// 错误:独立插件包已废弃
import { Selection } from '@antv/x6-plugin-selection';
import { History } from '@antv/x6-plugin-history';

// 正确:从 @antv/x6 直接导入
import { Graph, Selection, History } from '@antv/x6';
const graph = new Graph({ container: 'container' });
graph.use(new Selection({ enabled: true, rubberband: true }));
graph.use(new History({ enabled: true }));

❌ 在构造函数中传入插件选项

// 错误:3.x 不支持构造函数选项模式
const graph = new Graph({
  container: 'container',
  selecting: { enabled: true },  // ❌
  snapline: { enabled: true },   // ❌
  history: { enabled: true },    // ❌
});

// 正确:使用 graph.use() 注册插件
import { Graph, Selection, Snapline, History } from '@antv/x6';
const graph = new Graph({ container: 'container' });
graph.use(new Selection({ enabled: true }));
graph.use(new Snapline({ enabled: true }));
graph.use(new History({ enabled: true }));

❌ 混淆 CSS 属性和 SVG 属性

// 错误:使用 CSS 属性名
attrs: {
  body: {
    'background-color': '#fff',  // ❌
    'border-radius': '6px',      // ❌
  }
}

// 正确:使用 SVG 属性名
attrs: {
  body: {
    fill: '#fff',               // ✅ 背景色
    rx: 6,                      // ✅ 圆角
    ry: 6,
    stroke: '#8f8f8f',          // ✅ 边框色
    strokeWidth: 1,             // ✅ 边框宽度
  }
}

❌ 缺少 container

// 错误:遗漏 container
const graph = new Graph({});

// 正确:container 必填
const graph = new Graph({ container: 'container' });

❌ 连接桩未设置 magnet

// 错误:端口无法连线
ports: {
  items: [{ id: 'port1', group: 'out' }],
  groups: {
    out: { position: 'right', attrs: { circle: { r: 5 } } }
  }
}

// 正确:设置 magnet: true
ports: {
  items: [{ id: 'port1', group: 'out' }],
  groups: {
    out: { position: 'right', attrs: { circle: { r: 5, magnet: true, stroke: '#8f8f8f' } } }
  }
}

❌ 事件回调使用位置参数

// 错误:参数不是位置传递
graph.on('node:click', (node, e) => { ... });

// 正确:解构对象参数
graph.on('node:click', ({ node, e }) => { ... });

---

基础结构模板

import { Graph } from '@antv/x6';

const graph = new Graph({
  container: 'container',
  background: { color: '#F2F7FA' },
});

const source = graph.addNode({
  shape: 'rect',
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  label: 'Source',
  attrs: {
    body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 },
  },
});

const target = graph.addNode({
  shape: 'rect',
  x: 300,
  y: 200,
  width: 100,
  height: 40,
  label: 'Target',
  attrs: {
    body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 },
  },
});

graph.addEdge({
  source,
  target,
  attrs: {
    line: { stroke: '#8f8f8f', strokeWidth: 1 },
  },
});

// 内容居中:所有节点/边添加完成后调用,使画布内容相对于容器居中显示
// 如需缩放以适应容器,使用 graph.zoomToFit({ padding: 20, maxScale: 1 }) 替代
graph.centerContent();

---

场景选择指南

场景推荐配置关键特性
DAG 数据管道ports + orth router + connecting有向无环、端口连线
ER 实体关系图HTML 节点 + er router表格式节点、字段展示
流程图/审批流菱形判断节点 + 分支边条件分支、多路径
组织架构图orth router + 树形布局层级关系、折叠
血缘分析左右布局 + smooth connector多层流转、端口
网络拓扑圆形节点 + 星型结构设备类型、连接状态
状态机圆形节点 + 边标签状态转换、事件触发

---

内置节点类型

shape形状适用场景
rect矩形通用节点、流程步骤
circle圆形状态节点、端点
ellipse椭圆通用强调
polygon多边形菱形(判断)、六边形
text纯文本标注、注释
image图片图标节点
htmlHTML富文本、表格式节点

---

路由器与连接器

路由器(Router)— 决定边的路径走向

类型效果适用场景
normal直线(默认)简单图
orth正交折线流程图、DAG
manhattan智能正交(绕障)复杂布局
metro地铁线风格地铁图
erER 图专用实体关系图

连接器(Connector)— 决定边的线条样式

类型效果适用场景
normal直线段(默认)简单图
rounded圆角折线流程图(推荐)
smooth贝塞尔曲线血缘图、关系图
jumpover跨线跳跃复杂交叉

---

插件速查

插件注册方式功能
Selectiongraph.use(new Selection({ enabled: true, rubberband: true }))框选节点
Snaplinegraph.use(new Snapline({ enabled: true }))对齐辅助线
Historygraph.use(new History({ enabled: true }))撤销/重做
Clipboardgraph.use(new Clipboard({ enabled: true }))复制/粘贴
Keyboardgraph.use(new Keyboard({ enabled: true }))快捷键绑定
Scrollergraph.use(new Scroller({ enabled: true }))滚动画布
MiniMapgraph.use(new MiniMap({ enabled: true, container }))小地图导航
Transformgraph.use(new Transform({ resizing: { enabled: true }, rotating: { enabled: true } }))节点缩放/旋转
Exportgraph.use(new Export())导出 PNG/SVG
Stencilgraph.use(new Stencil({ target: graph, groups: [...] }))侧边栏拖拽面板
Dndgraph.use(new Dnd({ target: graph }))拖拽创建节点

Related skills

Frontend Developmentfrontendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.