Управление коннекторами в презентациях с использованием JavaScript
Обзор
Коннектор — это линия, которая может оставаться присоединённой к двум фигурам, когда любая из фигур перемещается. Его концы присоединяются к точкам соединения, обозначенным зелёными точками в PowerPoint. Некоторые изогнутые и гибкие коннекторы также имеют регулируемые точки, отображаемые оранжевыми точками, которые контролируют положение отдельных сегментов коннектора.
Aspose.Slides представляет коннекторы через класс Коннектор. Вы можете создавать их, присоединять их концы к фигурам, выбирать точки соединения, изменять маршрут и менять геометрию коннекторов, имеющих регулирующие точки.
Типы коннекторов
Класс ShapeType включает предустановки прямых, сгибных и изогнутых коннекторов. В таблице ниже показаны доступные геометрии коннекторов и количество регулирующих точек, определённых для каждой предустановки.
| Коннектор | Изображение | Количество регулирующих точек |
|---|---|---|
ShapeType.Line |
![]() |
0 |
ShapeType.StraightConnector1 |
![]() |
0 |
ShapeType.BentConnector2 |
![]() |
0 |
ShapeType.BentConnector3 |
![]() |
1 |
ShapeType.BentConnector4 |
![]() |
2 |
ShapeType.BentConnector5 |
![]() |
3 |
ShapeType.CurvedConnector2 |
![]() |
0 |
ShapeType.CurvedConnector3 |
![]() |
1 |
ShapeType.CurvedConnector4 |
![]() |
2 |
ShapeType.CurvedConnector5 |
![]() |
3 |
Количество и значение регулирующих точек являются частью выбранной предустановки коннектора. Не следует предполагать, что два разных типа коннекторов предоставляют одинаковую структуру коллекции.
Соединить две фигуры
Используйте ShapeCollection.addConnector для добавления коннектора и используйте Connector.setStartShapeConnectedTo и Connector.setEndShapeConnectedTo для присоединения его концов. После присоединения обоих концов Connector.reroute выбирает кратчайший маршрут между фигурами.
Следующий пример соединяет эллипс и прямоугольник с помощью сгибного коннектора:
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const ellipse = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Ellipse, 40, 80, 120, 80);
const rectangle = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 320, 240, 140, 80);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector2, 0, 0, 10, 10);
connector.setStartShapeConnectedTo(ellipse);
connector.setEndShapeConnectedTo(rectangle);
connector.reroute();
presentation.save("connected-shapes.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
Предупреждение
Вызовreroute может изменить значения setStartShapeConnectionSiteIndex и setEndShapeConnectionSiteIndex. Установите конкретные точки соединения после изменения маршрута, если эти точки должны оставаться фиксированными.
Выбор точки соединения
Каждая соединяемая фигура сообщает количество точек через Shape.getConnectionSiteCount. Проверьте желаемый индекс точки (нумерация с нуля) перед присвоением его коннектору; количество точек зависит от геометрии фигуры.
В этом примере коннектор присоединяется к определённой точке на эллипсе, если такая точка существует:
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const ellipse = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Ellipse, 40, 80, 120, 80);
const rectangle = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 320, 240, 140, 80);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector3, 0, 0, 10, 10;
connector.setStartShapeConnectedTo(ellipse);
connector.setEndShapeConnectedTo(rectangle);
const preferredSiteIndex = 2;
if (preferredSiteIndex < ellipse.getConnectionSiteCount()) {
connector.setStartShapeConnectionSiteIndex(preferredSiteIndex);
} else {
console.log(`The ellipse has only ${ellipse.getConnectionSiteCount()} connection sites.`);
}
presentation.save("specific-connection-site.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
Регулировка точки коннектора
Коннекторы с регулируемыми точками раскрывают их через GeometryShape.getAdjustments. Просмотрите каждое AdjustValue и проверьте его значение getType перед изменением с помощью setRawValue. Общие правила идентификации регулировок предустановленных фигур описаны в разделе Shape Manipulation.
Количество, порядок, смысл и допустимый диапазон значений регулировок коннектора зависят от предустановки коннектора. Тип регулировки только для чтения, тогда как значение можно изменять. Метод только для чтения getName предоставляет дополнительную идентификацию, когда у коннектора более одной регулировки одного и того же семантического типа.
Обход препятствия
На следующем макете коннектор BentConnector5 между двумя фигурами проходит через третью фигуру:

Этот код создаёт препятствующий коннектор:
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 300, 150, 150, 75);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 500, 400, 100, 50);
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 70, 30);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector5, 20, 20, 400, 300);
const black = java.getStaticFieldValue("java.awt.Color", "BLACK");
const solidFillType = java.newByte(aspose.slides.FillType.Solid);
const triangleArrowheadStyle = java.newByte(aspose.slides.LineArrowheadStyle.Triangle);
connector.getLineFormat().setEndArrowheadStyle(triangleArrowheadStyle);
connector.getLineFormat().getFillFormat().setFillType(solidFillType);
connector.getLineFormat().getFillFormat().getSolidFillColor().setColor(black);
connector.setStartShapeConnectedTo(sourceShape);
connector.setEndShapeConnectedTo(targetShape);
connector.setStartShapeConnectionSiteIndex(2);
presentation.save("connector-obstruction.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
Перемещение вертикального изгиба изменяет маршрут, так что коннектор обходится вокруг препятствия:

Вместо того чтобы предполагать, что индекс коллекции 1 всегда представляет вертикальный изгиб, данный пример ищет ConnectorBendPositionY и изменяет его только когда присутствует ожидаемый семантический тип:
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 300, 150, 150, 75);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 500, 400, 100, 50);
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 70, 30);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector5, 20, 20, 400, 300);
const black = java.getStaticFieldValue("java.awt.Color", "BLACK");
const solidFillType = java.newByte(aspose.slides.FillType.Solid);
const triangleArrowheadStyle = java.newByte(aspose.slides.LineArrowheadStyle.Triangle);
connector.getLineFormat().setEndArrowheadStyle(triangleArrowheadStyle);
connector.getLineFormat().getFillFormat().setFillType(solidFillType);
connector.getLineFormat().getFillFormat().getSolidFillColor().setColor(black);
connector.setStartShapeConnectedTo(sourceShape);
connector.setEndShapeConnectedTo(targetShape);
connector.setStartShapeConnectionSiteIndex(2);
let verticalBend = null;
for (let adjustmentIndex = 0; adjustmentIndex < connector.getAdjustments().size(); adjustmentIndex++) {
const adjustment = connector.getAdjustments().get_Item(adjustmentIndex);
console.log(`${adjustment.getName()}: ${adjustment.getType()}, raw value = ${adjustment.getRawValue()}`);
if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionY) {
verticalBend = adjustment;
break;
}
}
if (verticalBend === null) {
console.log("The connector does not expose a vertical bend adjustment.");
} else {
verticalBend.setRawValue(60000);
presentation.save("connector-obstruction-fixed.pptx", aspose.slides.SaveFormat.Pptx);
}
} finally {
presentation.dispose();
}
BentConnector5 содержит две регулировки ConnectorBendPositionX и одну ConnectorBendPositionY. Если нужный тип встречается более одного раза, проверьте getName и известную геометрию этой предустановки перед выбором. Если регулировка возвращает ShapeAdjustmentType.Custom, рассматривайте её смысл и диапазон как специфичные для предустановки и не изменяйте её, пока не будет известен соответствующий контракт.
Связание значений регулировок с геометрией коннектора
Для изгибных коннекторов значения регулировок можно использовать для оценки позиций отдельных сегментов. Эти расчёты специфичны для предустановки коннектора:
BentConnector4обычно имеет одну регулировкуConnectorBendPositionXи однуConnectorBendPositionY.- Для этих положений изгиба деление значения, возвращаемого
getRawValue, на100000даёт долю ширины или высоты рамки коннектора, используемую в примерах ниже. - Рамка коннектора может быть повернута или отражена, поэтому координаты рамки необходимо преобразовать перед сравнением с координатами слайда.
В следующих примерах сначала используется getType для определения регулировок. Индексы коллекции не рассматриваются как переносимые идентификаторы.
Неповернутый коннектор
Исходный макет содержит две текстовые фигуры, соединённые BentConnector4:

Этот пример проверяет коннектор и получает его горизонтальные и вертикальные регулировки изгиба:
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 60, 25);
sourceShape.getTextFrame().setText("From");
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 500, 100, 60, 25);
targetShape.getTextFrame().setText("To");
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector4, 20, 20, 400, 300);
const red = java.getStaticFieldValue("java.awt.Color", "RED");
const solidFillType = java.newByte(aspose.slides.FillType.Solid);
const triangleArrowheadStyle = java.newByte(aspose.slides.LineArrowheadStyle.Triangle);
connector.getLineFormat().setEndArrowheadStyle(triangleArrowheadStyle);
connector.getLineFormat().getFillFormat().setFillType(solidFillType);
connector.getLineFormat().getFillFormat().getSolidFillColor().setColor(red);
connector.getLineFormat().setWidth(3);
connector.setStartShapeConnectedTo(sourceShape);
connector.setStartShapeConnectionSiteIndex(3);
connector.setEndShapeConnectedTo(targetShape);
connector.setEndShapeConnectionSiteIndex(2);
for (let adjustmentIndex = 0; adjustmentIndex < connector.getAdjustments().size(); adjustmentIndex++) {
const adjustment = connector.getAdjustments().get_Item(adjustmentIndex);
console.log(`${adjustment.getName()}: ${adjustment.getType()}, raw value = ${adjustment.getRawValue()}`);
}
} finally {
presentation.dispose();
}
Чтобы изменить оба изгиба, найдите каждый ожидаемый тип и измените значения только после того, как оба будут найдены:
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 60, 25);
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 500, 100, 60, 25);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector4, 20, 20, 400, 300);
connector.setStartShapeConnectedTo(sourceShape);
connector.setStartShapeConnectionSiteIndex(3);
connector.setEndShapeConnectedTo(targetShape);
connector.setEndShapeConnectionSiteIndex(2);
let horizontalBend = null;
let verticalBend = null;
for (let adjustmentIndex = 0; adjustmentIndex < connector.getAdjustments().size(); adjustmentIndex++) {
const adjustment = connector.getAdjustments().get_Item(adjustmentIndex);
if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionX) {
horizontalBend = adjustment;
} else if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionY) {
verticalBend = adjustment;
}
}
if (horizontalBend === null || verticalBend === null) {
console.log("The connector does not expose the expected bend adjustments.");
} else {
horizontalBend.setRawValue(horizontalBend.getRawValue() + 20000);
verticalBend.setRawValue(verticalBend.getRawValue() + 200000);
presentation.save("connector-adjusted.pptx", aspose.slides.SaveFormat.Pptx);
}
} finally {
presentation.dispose();
}
В результате получаем коннектор, у которого горизонтальные и вертикальные сегменты сместились:

После определения семантических типов их значения можно преобразовать в координаты рамки коннектора. Этот пример рисует тонкий прямоугольник над вертикальным сегментом, контролируемым двумя регулировками изгиба:
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 60, 25);
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 500, 100, 60, 25);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector4, 20, 20, 400, 300);
connector.setStartShapeConnectedTo(sourceShape);
connector.setStartShapeConnectionSiteIndex(3);
connector.setEndShapeConnectedTo(targetShape);
connector.setEndShapeConnectionSiteIndex(2);
let horizontalBend = null;
let verticalBend = null;
for (let adjustmentIndex = 0; adjustmentIndex < connector.getAdjustments().size(); adjustmentIndex++) {
const adjustment = connector.getAdjustments().get_Item(adjustmentIndex);
if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionX) {
horizontalBend = adjustment;
} else if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionY) {
verticalBend = adjustment;
}
}
if (horizontalBend === null || verticalBend === null) {
console.log("The connector does not expose the expected bend adjustments.");
} else {
const x = connector.getX() + connector.getWidth() * horizontalBend.getRawValue() / 100000;
const y = connector.getY();
const height = connector.getHeight() * verticalBend.getRawValue() / 100000;
const guideX = java.newFloat(x);
const guideY = java.newFloat(y);
const guideWidth = java.newFloat(1);
const guideHeight = java.newFloat(height);
slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, guideX, guideY, guideWidth, guideHeight);
presentation.save("connector-segment-guide.pptx", aspose.slides.SaveFormat.Pptx);
}
} finally {
presentation.dispose();
}
Форма‑направляющая отмечает вычисленный сегмент:

Повернутый или отражённый коннектор
Когда та же геометрия коннектора ориентирована вертикально, её значения Shape.getFrame, ShapeFrame.getFlipH, и ShapeFrame.getFlipV влияют на преобразование координат рамки коннектора в координаты слайда.
Этот пример создаёт и регулирует вертикально ориентированный коннектор:
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 60, 25);
sourceShape.getTextFrame().setText("From");
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 400, 60, 25);
targetShape.getTextFrame().setText("To 1");
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector4, 20, 20, 400, 300);
const connectorColor = java.newInstanceSync("java.awt.Color", 102, 205, 170);
const solidFillType = java.newByte(aspose.slides.FillType.Solid);
const triangleArrowheadStyle = java.newByte(aspose.slides.LineArrowheadStyle.Triangle);
connector.getLineFormat().setEndArrowheadStyle(triangleArrowheadStyle);
connector.getLineFormat().getFillFormat().setFillType(solidFillType);
connector.getLineFormat().getFillFormat().getSolidFillColor().setColor(connectorColor);
connector.getLineFormat().setWidth(3);
connector.setStartShapeConnectedTo(sourceShape);
connector.setStartShapeConnectionSiteIndex(2);
connector.setEndShapeConnectedTo(targetShape);
connector.setEndShapeConnectionSiteIndex(3);
for (let adjustmentIndex = 0; adjustmentIndex < connector.getAdjustments().size(); adjustmentIndex++) {
const adjustment = connector.getAdjustments().get_Item(adjustmentIndex);
if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionX) {
adjustment.setRawValue(adjustment.getRawValue() + 20000);
} else if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionY) {
adjustment.setRawValue(adjustment.getRawValue() + 200000);
}
}
presentation.save("vertical-connector-adjusted.pptx", aspose.slides.SaveFormat.Pptx);
} finally {
presentation.dispose();
}
Отрегулированный коннектор отображается вертикально между фигурами:

Для произвольного угла поворота alpha поверните точку рамки коннектора (x, y) вокруг центра рамки (x0, y0):
X = (x - x0) * cos(alpha) - (y - y0) * sin(alpha) + x0
Y = (x - x0) * sin(alpha) + (y - y0) * cos(alpha) + y0
Следующий код обрабатывает 90‑градусную ориентацию, использованную в этом примере, и рисует красную направляющую над соответствующим сегментом коннектора:
const aspose = { slides: require("aspose.slides.via.java") };
const java = require("java");
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const sourceShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 100, 60, 25);
const targetShape = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, 100, 400, 60, 25);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.BentConnector4, 20, 20, 400, 300);
connector.setStartShapeConnectedTo(sourceShape);
connector.setStartShapeConnectionSiteIndex(2);
connector.setEndShapeConnectedTo(targetShape);
connector.setEndShapeConnectionSiteIndex(3);
let horizontalBend = null;
let verticalBend = null;
for (let adjustmentIndex = 0; adjustmentIndex < connector.getAdjustments().size(); adjustmentIndex++) {
const adjustment = connector.getAdjustments().get_Item(adjustmentIndex);
if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionX) {
horizontalBend = adjustment;
} else if (adjustment.getType() === aspose.slides.ShapeAdjustmentType.ConnectorBendPositionY) {
verticalBend = adjustment;
}
}
if (horizontalBend === null || verticalBend === null) {
console.log("The connector does not expose the expected bend adjustments.");
} else {
horizontalBend.setRawValue(horizontalBend.getRawValue() + 20000);
verticalBend.setRawValue(verticalBend.getRawValue() + 200000);
let x = connector.getX();
let y = connector.getY();
if (connector.getFrame().getFlipH() === aspose.slides.NullableBool.True) {
x += connector.getWidth();
}
if (connector.getFrame().getFlipV() === aspose.slides.NullableBool.True) {
y += connector.getHeight();
}
x += connector.getWidth() * horizontalBend.getRawValue() / 100000;
const rotatedX = connector.getFrame().getCenterX() - y + connector.getFrame().getCenterY();
const rotatedY = x - connector.getFrame().getCenterX() + connector.getFrame().getCenterY();
const segmentWidth = connector.getHeight() * verticalBend.getRawValue() / 100000;
const guideX = java.newFloat(rotatedX);
const guideY = java.newFloat(rotatedY);
const guideWidth = java.newFloat(segmentWidth);
const guideHeight = java.newFloat(1);
const guide = slide.getShapes().addAutoShape(aspose.slides.ShapeType.Rectangle, guideX, guideY, guideWidth, guideHeight);
const red = java.getStaticFieldValue("java.awt.Color", "RED");
const solidFillType = java.newByte(aspose.slides.FillType.Solid);
guide.getLineFormat().getFillFormat().setFillType(solidFillType);
guide.getLineFormat().getFillFormat().getSolidFillColor().setColor(red);
presentation.save("rotated-connector-segment-guide.pptx", aspose.slides.SaveFormat.Pptx);
}
} finally {
presentation.dispose();
}
Красная направляющая отмечает вычисленный сегмент после преобразования координат:

Эти формулы описывают предустановки, использованные в примерах, а не универсальную модель коннектора. Проверьте типы регулировок, ориентацию рамки и диапазоны значений перед применением тех же расчётов к другой предустановке.
Найти угол направления коннектора
Направление прямого коннектора можно вычислить по его ширине и высоте с учётом горизонтального и вертикального отражения. В следующем примере выводится угол по часовой стрелке от положительной горизонтальной оси в координатах слайда:
const aspose = { slides: require("aspose.slides.via.java") };
const presentation = new aspose.slides.Presentation();
try {
const slide = presentation.getSlides().get_Item(0);
const connector = slide.getShapes().addConnector(aspose.slides.ShapeType.StraightConnector1, 100, 100, 200, 100);
const flipH = connector.getFrame().getFlipH() === aspose.slides.NullableBool.True;
const flipV = connector.getFrame().getFlipV() === aspose.slides.NullableBool.True;
const deltaX = connector.getWidth() * (flipH ? -1 : 1);
const deltaY = connector.getHeight() * (flipV ? -1 : 1);
let angle = Math.atan2(deltaY, deltaX) * 180.0 / Math.PI;
if (angle < 0) {
angle += 360;
}
console.log(`Connector direction: ${angle.toFixed(2)} degrees`);
} finally {
presentation.dispose();
}
ЧаВо
Как понять, может ли коннектор присоединяться к фигуре?
Проверьте значение getConnectionSiteCount у фигуры. Положительное значение означает, что фигура предоставляет точки соединения. Проверьте выбранный индекс точки перед присвоением его концу коннектора.
Можно ли идентифицировать регулировку коннектора по её индексу в коллекции?
Индекс имеет смысл только для известной предустановки коннектора и структуры коллекции. Проверьте AdjustValue.getType перед изменением значения и используйте AdjustValue.getName как дополнительную информацию, когда один и тот же семантический тип встречается более одного раза.
Что происходит, когда соединённая фигура удаляется?
Соответствующий конец коннектора отсоединяется. Коннектор остаётся на слайде и может быть удалён, размещён как свободная линия или присоединён к другой фигуре.
Сохраняются ли привязки коннектора при копировании слайда?
Привязки обычно сохраняются при копировании слайда вместе с соединёнными фигурами. Если коннектор копируется без одной из целевых фигур, соответствующий конец необходимо присоединить заново.









