Add new line type support: Arc.

This commit is contained in:
Paulo Gustavo Veiga 2023-02-11 18:55:46 -08:00
parent 2de5f045fa
commit 169fecc172
21 changed files with 392 additions and 23 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
import React from 'react';
import BrushOutlinedIcon from '@mui/icons-material/BrushOutlined';
import FormatPaintIconOutlineIcon from '@mui/icons-material/FormatPaintOutlined';
import FontDownloadOutlinedIcon from '@mui/icons-material/FontDownloadOutlined';
import TextIncreaseOutlinedIcon from '@mui/icons-material/TextIncreaseOutlined';
import TextDecreaseOutlinedIcon from '@mui/icons-material/TextDecreaseOutlined';
@ -38,6 +38,7 @@ import TimelineOutined from '@mui/icons-material/TimelineOutlined';
import ShareOutlined from '@mui/icons-material/ShareOutlined';
import SwapCallsOutlined from '@mui/icons-material/SwapCallsOutlined';
import NotInterestedOutlined from '@mui/icons-material/NotInterestedOutlined';
import ShortcutIconOutlined from '@mui/icons-material/ShortcutOutlined';
import Palette from '@mui/icons-material/Square';
import SquareOutlined from '@mui/icons-material/SquareOutlined';
@ -62,7 +63,7 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo
const modelBuilder = new NodePropertyValueModelBuilder(model.getDesigner());
// eslint-disable-next-line react-hooks/rules-of-hooks
const colorAndShapeToolbarConfiguration: ActionConfig = {
icon: <BrushOutlinedIcon />,
icon: <FormatPaintIconOutlineIcon />,
tooltip: intl.formatMessage({
id: 'editor-panel.tooltip-topic-style',
defaultMessage: 'Topic Style',
@ -182,6 +183,15 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo
onClick: () => modelBuilder.getConnectionStyleModel().setValue(LineType.THICK_CURVED),
selected: () => modelBuilder.getConnectionStyleModel().getValue() === LineType.THICK_CURVED,
},
{
icon: <ShortcutIconOutlined />,
tooltip: intl.formatMessage({
id: 'editor-panel.tooltip-connection-style-arc',
defaultMessage: 'Arc',
}),
onClick: () => modelBuilder.getConnectionStyleModel().setValue(LineType.ARC),
selected: () => modelBuilder.getConnectionStyleModel().getValue() === LineType.ARC,
},
{
icon: <SwapCallsOutlined />,
tooltip: intl.formatMessage({
@ -444,9 +454,9 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo
return [
addNodeToolbarConfiguration,
deleteNodeToolbarConfiguration,
connectionStyleConfiguration,
fontFormatToolbarConfiguration,
colorAndShapeToolbarConfiguration,
fontFormatToolbarConfiguration,
connectionStyleConfiguration,
editIconConfiguration,
editNoteConfiguration,
editLinkUrlConfiguration,

View File

@ -22,6 +22,7 @@ import PositionType from './PositionType';
import Topic from './Topic';
import TopicConfig from './TopicConfig';
import Canvas from './Canvas';
import ArcLine from './widget/ArcLine';
// eslint-disable-next-line no-shadow
export enum LineType {
@ -29,6 +30,7 @@ export enum LineType {
POLYLINE_MIDDLE,
POLYLINE_CURVED,
THICK_CURVED,
ARC,
}
class ConnectionLine {
@ -81,8 +83,13 @@ class ConnectionLine {
line = new CurvedLine();
(line as CurvedLine).setWidth(this._targetTopic.isCentralTopic() ? 15 : 3);
break;
default:
throw new Error(`Unexpected line type. ${lineType}`);
case LineType.ARC:
line = new ArcLine(this._sourceTopic, this._targetTopic);
break;
default: {
const exhaustiveCheck: never = lineType;
throw new Error(exhaustiveCheck);
}
}
return line;
}
@ -110,8 +117,13 @@ class ConnectionLine {
this._line.setStroke(1, 'solid', color, 1);
this._line.setFill(color, 1);
break;
default:
throw new Error(`Unexpected line type. ${this._type}`);
case LineType.ARC:
this._line.setStroke(1, 'solid', color, 1);
break;
default: {
const exhaustiveCheck: never = this._type;
throw new Error(exhaustiveCheck);
}
}
return color;
}

View File

@ -890,10 +890,9 @@ class Designer extends Events {
}
changeConnectionStyle(type: LineType): void {
const validateFunc = (topic: Topic) => !topic.isCentralTopic();
const validateError = $msg('CENTRAL_TOPIC_CONNECTION_STYLE_CAN_NOT_BE_CHANGED');
const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError);
const topicsIds = this.getModel()
.filterSelectedTopics()
.map((t) => t.getId());
if (topicsIds.length > 0) {
this._actionDispatcher.changeConnectionStyleToTopic(topicsIds, type);
}

View File

@ -277,8 +277,6 @@ class Relationship extends ConnectionLine {
this._focusShape.setSrcControlPoint(ctrlPoints[0]);
this._focusShape.setDestControlPoint(ctrlPoints[1]);
this._focusShape.updateLine();
}
addEvent(eventType: string, listener: () => void) {

View File

@ -0,0 +1,46 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* 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.
*/
import { ArcLine as ArcLine2d } from '@wisemapping/web2d';
import Topic from '../Topic';
class ArcLine extends ArcLine2d {
private _targetTopic: Topic;
private _sourceTopic: Topic;
constructor(sourceTopic: Topic, targetTopic: Topic) {
super();
this._targetTopic = targetTopic;
this._sourceTopic = sourceTopic;
}
// Adjust the x position so there is not overlap with the connector.
setFrom(x: number, y: number): void {
let xOffset = x;
if (this._targetTopic.isCentralTopic()) {
const sourceX = this._sourceTopic.getPosition().x;
xOffset = Math.sign(sourceX) * (this._targetTopic.getSize().width / 3);
} else {
xOffset = x + 3 * Math.sign(x);
}
super.setFrom(xOffset, y);
}
}
export default ArcLine;

View File

@ -20,7 +20,6 @@
"dev": "webpack --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"lint": "eslint src",
"playground": "webpack serve --config webpack.playground.js",
"cy:run": "cypress run",
"cy:open": "cypress open",
"test:integration": "start-server-and-test storybook http-get://localhost:6006 cy:run",

View File

@ -0,0 +1,103 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* 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.
*/
import { $assert } from '@wisemapping/core-js';
import WorkspaceElement from './WorkspaceElement';
import Line from './Line';
import StyleAttributes from './StyleAttributes';
import Toolkit from './Toolkit';
import ArcLinePeer from './peer/svg/ArcLinePeer';
import PositionType from './PositionType';
class ArcLine extends WorkspaceElement<ArcLinePeer> implements Line {
constructor(attributes?: StyleAttributes) {
const peer = Toolkit.createArcLine();
const defaultAttributes = {
strokeColor: 'blue',
strokeWidth: 1,
strokeStyle: 'solid',
strokeOpacity: 1,
fill: 'none 0',
};
const mergedAttr = { ...defaultAttributes, ...attributes };
super(peer, mergedAttr);
}
getType(): string {
return 'ArcLine';
}
setFrom(x: number, y: number): void {
$assert(!Number.isNaN(x), 'x must be defined');
$assert(!Number.isNaN(y), 'y must be defined');
this.peer.setFrom(x, y);
}
setTo(x: number, y: number): void {
$assert(!Number.isNaN(x), 'x must be defined');
$assert(!Number.isNaN(y), 'y must be defined');
this.peer.setTo(x, y);
}
getFrom() {
return this.peer.getFrom();
}
getTo() {
return this.peer.getTo();
}
getElementClass(): ArcLine {
return this;
}
setIsSrcControlPointCustom(value: boolean): void {
throw new Error('Method not implemented.');
}
setIsDestControlPointCustom(value: boolean): void {
throw new Error('Method not implemented.');
}
setDashed(v: number, v2: number): void {
throw new Error('Method not implemented.');
}
setSrcControlPoint(value: PositionType): void {
throw new Error('Method not implemented.');
}
setDestControlPoint(value: PositionType): void {
throw new Error('Method not implemented.');
}
isDestControlPointCustom(): boolean {
throw new Error('Method not implemented.');
}
isSrcControlPointCustom(): boolean {
throw new Error('Method not implemented.');
}
getControlPoints(): [PositionType, PositionType] {
throw new Error('Method not implemented.');
}
}
export default ArcLine;

View File

@ -70,8 +70,6 @@ interface Line {
removeEvent(value, listener): void;
updateLine(): void;
getElementClass(): WorkspaceElement<ElementPeer>;
}
export default Line;

View File

@ -42,9 +42,6 @@ class PolyLine extends WorkspaceElement<PolyLinePeer> implements Line {
return this;
}
updateLine() {
throw new Error('Method not implemented.');
}
getTo(): PositionType {
throw new Error('Method not implemented.');
}

View File

@ -36,9 +36,6 @@ class StraightLine extends WorkspaceElement<StraightLinePeer> implements Line {
return this;
}
updateLine() {
throw new Error('Method not implemented.');
}
setIsSrcControlPointCustom(value: boolean): void {
throw new Error('Method not implemented.');
}

View File

@ -26,6 +26,7 @@ import ArrowPeer from './peer/svg/ArrowPeer';
import TextPeer from './peer/svg/TextPeer';
import ImagePeer from './peer/svg/ImagePeer';
import RectPeer from './peer/svg/RectPeer';
import ArcLinePeer from './peer/svg/ArcLinePeer';
class Toolkit {
static createWorkspace(element?: HTMLElement) {
@ -52,6 +53,10 @@ class Toolkit {
return new CurvedLinePeer();
}
static createArcLine(): ArcLinePeer {
return new ArcLinePeer();
}
static createArrow() {
return new ArrowPeer();
}

View File

@ -0,0 +1,85 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* 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.
*/
import { $defined } from '@wisemapping/core-js';
import PositionType from '../../PositionType';
import ElementPeer from './ElementPeer';
class ArcLinePeer extends ElementPeer {
private _x1: number;
private _y1: number;
private _x2: number;
private _y2: number;
constructor() {
const svgElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'path');
super(svgElement);
this._x1 = 0;
this._x2 = 0;
this._y1 = 0;
this._y2 = 0;
this._updatePath();
}
setFrom(x1: number, y1: number): void {
const change = this._x1 !== x1 || this._y1 !== y1;
this._x1 = x1;
this._y1 = y1;
if (change) {
this._updatePath();
}
}
setTo(x2: number, y2: number) {
const change = this._x2 !== x2 || this._y2 !== y2;
this._x2 = x2;
this._y2 = y2;
if (change) this._updatePath();
}
getFrom(): PositionType {
return { x: this._x1, y: this._y1 };
}
getTo(): PositionType {
return { x: this._x2, y: this._y2 };
}
setStrokeWidth(width: number): void {
this._native.setAttribute('stroke-width', String(width));
}
private static pointToStr(x: number, y: number) {
return `${x.toFixed(1)},${y.toFixed(1)} `;
}
private _updatePath() {
// Update style based on width ....
if ($defined(this._x1) && $defined(this._y1) && $defined(this._x2) && $defined(this._y2)) {
const fromPoint = ArcLinePeer.pointToStr(this._x1, this._y1);
const toPoint = ArcLinePeer.pointToStr(this._x2, this._y2);
const curveP1 = ArcLinePeer.pointToStr(this._x1, this._y1 + (this._y2 - this._y1) / 8);
const curveP2 = ArcLinePeer.pointToStr(this._x2 - (this._x2 - this._x1), this._y2);
const path = `M${fromPoint} C${curveP1},${curveP2} ${toPoint}`;
this._native.setAttribute('d', path);
}
}
}
export default ArcLinePeer;

View File

@ -27,6 +27,7 @@ import Rect from './components/Rect';
import Text from './components/Text';
import Point from './components/Point';
import Image from './components/Image';
import ArcLine from './components/ArcLine';
import WorkspaceElement from './components/WorkspaceElement';
import Line from './components/Line';
import ElementPeer from './components/peer/svg/ElementPeer';
@ -42,6 +43,7 @@ export {
StraightLine,
Point,
PolyLine,
ArcLine,
Rect,
Text,
Workspace,

View File

@ -0,0 +1,86 @@
/* eslint-disable import/prefer-default-export */
// eslint-disable-next-line import/prefer-default-export
import Ellipse from '../../../src/components/Ellipse';
import Workspace from '../../../src/components/Workspace';
import ArcLine from '../../../src/components/ArcLine';
export const createArcLine = ({
strokeColor,
strokeWidth,
strokeStyle,
}) => {
const divElem = document.createElement('div');
const workspace = new Workspace();
workspace.setSize('400px', '400px');
workspace.setCoordSize(400, 400);
workspace.setCoordOrigin(-200, -200);
// Line 1 ...
const line1 = new ArcLine();
line1.setFrom(0, 0);
line1.setTo(100, 100);
line1.setStroke(strokeWidth, strokeStyle, strokeColor, 1);
workspace.append(line1);
const line2 = new ArcLine();
line2.setFrom(0, 0);
line2.setTo(-100, -100);
line2.setStroke(strokeWidth, strokeStyle, strokeColor, 1);
workspace.append(line2);
const line3 = new ArcLine();
line3.setFrom(0, 0);
line3.setTo(100, -100);
line3.setStroke(strokeWidth, strokeStyle, strokeColor, 1);
workspace.append(line3);
const line4 = new ArcLine();
line4.setFrom(0, 0);
line4.setTo(-100, 100);
line4.setStroke(strokeWidth, strokeStyle, strokeColor, 1);
workspace.append(line4);
const line5 = new ArcLine();
line5.setFrom(0, 0);
line5.setTo(-100, 0);
line5.setStroke(strokeWidth, strokeStyle, strokeColor, 1);
workspace.append(line5);
const line6 = new ArcLine();
line6.setFrom(0, 0);
line6.setTo(100, 0);
line6.setStroke(strokeWidth, strokeStyle, strokeColor, 1);
workspace.append(line6);
// Add referene point ...
const e1 = new Ellipse();
e1.setSize(5, 5);
e1.setPosition(0, 0);
e1.setFill('red');
workspace.append(e1);
const e2 = new Ellipse();
e2.setPosition(-100, -100);
e2.setSize(10, 10);
workspace.append(e2);
const e3 = new Ellipse();
e3.setPosition(100, 100);
e3.setSize(10, 10);
workspace.append(e3);
const e4 = new Ellipse();
e4.setPosition(-100, 100);
e4.setSize(10, 10);
workspace.append(e4);
const e5 = new Ellipse();
e5.setPosition(100, -100);
e5.setSize(10, 10);
workspace.append(e5);
workspace.addItAsChildTo(divElem);
return divElem;
};

View File

@ -0,0 +1,32 @@
import { createArcLine } from './ArcLine';
// More on default export: https://storybook.js.org/docs/html/writing-stories/introduction#default-export
export default {
title: 'Shapes/ArcLine',
// More on argTypes: https://storybook.js.org/docs/html/api/argtypes
argTypes: {
strokeColor: { control: 'color' },
strokeStyle: {
control: { type: 'select' },
options: ['dash', 'dot', 'solid', 'longdash', 'dashdot'],
},
strokeWidth: { control: { type: 'number', min: 0, max: 30, step: 1 } },
},
};
// More on component templates: https://storybook.js.org/docs/html/writing-stories/introduction#using-args
const Template = ({ label, ...args }) => createArcLine({ label, ...args });
export const Width = Template.bind({});
Width.args = {
strokeWidth: 3,
strokeStyle: 'solid',
strokeColor: 'blue',
};
export const Stroke = Template.bind({});
Stroke.args = {
strokeWidth: 10,
strokeStyle: 'longdash',
strokeColor: 'red',
};