You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改mxGraph中的旋转控制点(Rotation Handler)图标

如何替换mxGraph中选中图形的旋转控制点图标

我来帮你搞定替换mxGraph旋转控制点图标的问题~ 你现在的代码已经开启了旋转功能,接下来只需要重写旋转手柄的绘制逻辑,就能换成你想要的自定义图标。

核心思路

mxGraph的旋转控制点是由mxVertexHandler创建的旋转手柄控制的,我们可以通过重写这个手柄的paint方法,自定义它的外观——不管是画自定义形状,还是用图片当图标都可以。

修改后的完整代码

下面是整合了自定义旋转图标的完整代码,我标注了关键修改部分:

<!-- Copyright (c) 2006-2014, JGraph Ltd Handles example for mxGraph. This example demonstrates using mxHandle to change custom styles interactively. -->
<html>
<head>
<title>Handles example for mxGraph</title>
<!-- Sets the basepath for the library if not in same directory -->
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
</script>
<!-- Loads and initializes the library -->
<script type="text/javascript" src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js"></script>
<!-- Example code -->
<script type="text/javascript">
function MyShape() {
mxCylinder.call(this);
};
mxUtils.extend(MyShape, mxCylinder);
MyShape.prototype.defaultPos1 = 20;
MyShape.prototype.defaultPos2 = 60;
MyShape.prototype.getLabelBounds = function(rect) {
var pos1 = mxUtils.getValue(this.style, 'pos1', this.defaultPos1) * this.scale;
var pos2 = mxUtils.getValue(this.style, 'pos2', this.defaultPos2) * this.scale;
return new mxRectangle(rect.x, rect.y + pos1, rect.width, Math.min(rect.height, pos2) - Math.max(0, pos1));
};
MyShape.prototype.redrawPath = function(path, x, y, w, h, isForeground) {
var pos1 = mxUtils.getValue(this.style, 'pos1', this.defaultPos1);
var pos2 = mxUtils.getValue(this.style, 'pos2', this.defaultPos2);
if (isForeground) {
if (pos1 < h) {
path.moveTo(0, pos1);
path.lineTo(w, pos1);
}
if (pos2 < h) {
path.moveTo(0, pos2);
path.lineTo(w, pos2);
}
} else {
path.rect(0, 0, w, h);
}
};
mxCellRenderer.registerShape('myShape', MyShape);
mxVertexHandler.prototype.createCustomHandles = function() {
if (this.state.style['shape'] == 'myShape') {
// Implements the handle for the first divider
var firstHandle = new mxHandle(this.state);
firstHandle.getPosition = function(bounds) {
var pos2 = Math.max(0, Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'pos2', MyShape.prototype.defaultPos2))));
var pos1 = Math.max(0, Math.min(pos2, parseFloat(mxUtils.getValue(this.state.style, 'pos1', MyShape.prototype.defaultPos1))));
return new mxPoint(bounds.getCenterX(), bounds.y + pos1);
};
firstHandle.setPosition = function(bounds, pt) {
var pos2 = Math.max(0, Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'pos2', MyShape.prototype.defaultPos2))));
this.state.style['pos1'] = Math.round(Math.max(0, Math.min(pos2, pt.y - bounds.y)));
};
firstHandle.execute = function() {
this.copyStyle('pos1');
}
firstHandle.ignoreGrid = true;
// Implements the handle for the second divider
var secondHandle = new mxHandle(this.state);
secondHandle.getPosition = function(bounds) {
var pos1 = Math.max(0, Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'pos1', MyShape.prototype.defaultPos1))));
var pos2 = Math.max(pos1, Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'pos2', MyShape.prototype.defaultPos2))));
return new mxPoint(bounds.getCenterX(), bounds.y + pos2);
};
secondHandle.setPosition = function(bounds, pt) {
var pos1 = Math.max(0, Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'pos1', MyShape.prototype.defaultPos1))));
this.state.style['pos2'] = Math.round(Math.max(pos1, Math.min(bounds.height, pt.y - bounds.y)));
};
secondHandle.execute = function() {
this.copyStyle('pos2');
}
secondHandle.ignoreGrid = true;
return [firstHandle, secondHandle];
}
return null;
};
mxVertexHandler.prototype.livePreview = true;
mxVertexHandler.prototype.rotationEnabled = true;

// -------------------------- 关键修改部分 --------------------------
// 重写旋转手柄,替换为自定义图标
mxVertexHandler.prototype.createRotationHandle = function() {
  // 先调用原方法创建基础手柄
  var handle = mxVertexHandler.prototype.createRotationHandle.apply(this, arguments);
  
  // 重写paint方法,自定义绘制逻辑
  handle.paint = function(c, x, y, w, h) {
    // 示例:绘制蓝色圆形+白色箭头的旋转图标
    // 设置填充色为蓝色,边框为白色
    c.setFillColor('#0078d7');
    c.setStrokeColor('#ffffff');
    c.setStrokeWidth(2);
    // 绘制圆形
    c.ellipse(x - w/2, y - h/2, w, h);
    c.fillAndStroke();
    
    // 绘制白色箭头
    c.setFillColor('#ffffff');
    c.begin();
    c.moveTo(x + w/4, y - h/4);
    c.lineTo(x + w/2, y);
    c.lineTo(x + w/4, y + h/4);
    c.close();
    c.fill();
  };
  
  // 如果想用自定义图片作为图标,替换上面的paint方法为下面这段:
  /*
  handle.image = new Image();
  handle.image.src = 'your-custom-icon.png'; // 替换成你的图片路径
  handle.paint = function(c, x, y, w, h) {
    // 确保图片加载完成再绘制
    if (this.image.complete) {
      c.drawImage(this.image, x - w/2, y - h/2, w, h);
    }
  };
  */
  
  return handle;
};
// -------------------------- 关键修改结束 --------------------------

// Program starts here. Creates a sample graph in the
// DOM node with the specified ID. This function is invoked
// from the onLoad event handler of the document (see below).
function main(container) {
// Checks if the browser is supported
if (!mxClient.isBrowserSupported()) {
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
} else {
// Disables the built-in context menu
mxEvent.disableContextMenu(container);
// Creates the graph inside the given container
var graph = new mxGraph(container);
graph.setCellsCloneable(true);
graph.setHtmlLabels(true);
graph.setPanning(true);
graph.centerZoom = false;
// Enables rubberband selection
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try {
var v1 = graph.insertVertex(parent, null, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 20, 20, 240, 120, 'shape=myShape;whiteSpace=wrap;overflow=hidden;pos1=30;pos2=80;');
} finally {
// Updates the display
graph.getModel().endUpdate();
}
document.body.appendChild(mxUtils.button('+', function() {
graph.zoomIn();
}));
document.body.appendChild(mxUtils.button('-', function() {
graph.zoomOut();
}));
}
};
</script>
</head>
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer" style="position:relative;overflow:hidden;width:621px;height:441px;background:url('editors/images/grid.gif');cursor:default;">
</div>
</body>
</html>

代码说明

  1. 重写createRotationHandle方法:我们先保留原方法创建的手柄基础功能,只替换它的绘制逻辑,这样不会影响旋转的交互功能。
  2. 自定义形状绘制:示例中画了一个蓝色圆形加白色箭头,你可以调整颜色、大小、形状,完全按照你的需求修改绘制代码。
  3. 图片图标替换:如果想用图片当图标,注释掉形状绘制的代码,启用图片相关的代码,替换成你的图片路径即可(注意图片路径的正确性,避免跨域问题)。

现在你运行代码,选中那个自定义矩形后,旋转控制点就会变成你设置的自定义样式啦~

内容的提问来源于stack exchange,提问作者user6783030

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:17:20