From 7977fcee42f4beeeb25d4fa50592c031a32be03e Mon Sep 17 00:00:00 2001 From: Paulo Gustavo Veiga Date: Sat, 31 Dec 2022 12:45:15 -0800 Subject: [PATCH] Extract shape topics as classes. Fix bugs on color connection render --- .../mindplot/src/components/CentralTopic.ts | 2 +- .../mindplot/src/components/ConnectionLine.ts | 7 +- packages/mindplot/src/components/MainTopic.ts | 10 +- packages/mindplot/src/components/NodeGraph.ts | 8 +- .../src/components/ShrinkConnector.ts | 4 +- packages/mindplot/src/components/Topic.ts | 128 ++++++++---------- .../mindplot/src/components/TopicFactory.ts | 3 +- .../src/components/model/NodeModel.ts | 10 -- .../src/components/widget/ImageTopicShape.ts | 45 ++++++ .../src/components/widget/LineTopicShape.ts | 55 ++++++++ 10 files changed, 179 insertions(+), 93 deletions(-) create mode 100644 packages/mindplot/src/components/widget/ImageTopicShape.ts create mode 100644 packages/mindplot/src/components/widget/LineTopicShape.ts diff --git a/packages/mindplot/src/components/CentralTopic.ts b/packages/mindplot/src/components/CentralTopic.ts index 1fc61e92..600feb63 100644 --- a/packages/mindplot/src/components/CentralTopic.ts +++ b/packages/mindplot/src/components/CentralTopic.ts @@ -30,7 +30,7 @@ class CentralTopic extends Topic { // This disable the drag of the central topic. // But solves the problem of deselecting the nodes when the screen is clicked. - this.addEvent('mousedown', (event) => { + this.addEvent('mousedown', (event: MouseEvent) => { event.stopPropagation(); }); } diff --git a/packages/mindplot/src/components/ConnectionLine.ts b/packages/mindplot/src/components/ConnectionLine.ts index 5f38b850..f2e43b55 100644 --- a/packages/mindplot/src/components/ConnectionLine.ts +++ b/packages/mindplot/src/components/ConnectionLine.ts @@ -89,7 +89,12 @@ class ConnectionLine { } private updateColor(): void { - const color = this._targetTopic.getConnectionColor(); + // In case that the main topic has changed the color, overwrite the main topic definiton. + let color = this._targetTopic.getConnectionColor(); + if (this._targetTopic.isCentralTopic()) { + color = this._sourceTopic.getModel().getConnectionColor() || color; + } + this._color = color; switch (this._lineType) { case LineType.POLYLINE_MIDDLE: diff --git a/packages/mindplot/src/components/MainTopic.ts b/packages/mindplot/src/components/MainTopic.ts index bc92b19f..086dc04e 100644 --- a/packages/mindplot/src/components/MainTopic.ts +++ b/packages/mindplot/src/components/MainTopic.ts @@ -23,11 +23,13 @@ import Shape from './util/Shape'; import NodeModel from './model/NodeModel'; import Workspace from './Workspace'; import SizeType from './SizeType'; +import PositionType from './PositionType'; +import { NodeOption } from './NodeGraph'; class MainTopic extends Topic { private INNER_RECT_ATTRIBUTES: { stroke: string }; - constructor(model: NodeModel, options) { + constructor(model: NodeModel, options: NodeOption) { super(model, options); this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' }; } @@ -112,11 +114,11 @@ class MainTopic extends Topic { } } - workoutIncomingConnectionPoint(sourcePosition: Point) { + workoutIncomingConnectionPoint(sourcePosition: PositionType): PositionType { return Shape.workoutIncomingConnectionPoint(this, sourcePosition); } - workoutOutgoingConnectionPoint(targetPosition: Point) { + workoutOutgoingConnectionPoint(targetPosition: PositionType): PositionType { $assert(targetPosition, 'targetPoint can not be null'); const pos = this.getPosition(); const isAtRight = Shape.isAtRight(targetPosition, pos); @@ -148,7 +150,7 @@ class MainTopic extends Topic { } else { result = Shape.calculateRectConnectionPoint(pos, size, isAtRight); } - return new Point(result.x, result.y); + return { ...result }; } } diff --git a/packages/mindplot/src/components/NodeGraph.ts b/packages/mindplot/src/components/NodeGraph.ts index d0a00db6..7435cbeb 100644 --- a/packages/mindplot/src/components/NodeGraph.ts +++ b/packages/mindplot/src/components/NodeGraph.ts @@ -24,10 +24,14 @@ import DragTopic from './DragTopic'; import LayoutManager from './layout/LayoutManager'; import SizeType from './SizeType'; +export type NodeOption = { + readOnly: boolean; +}; + abstract class NodeGraph { private _mouseEvents: boolean; - private _options; + private _options: NodeOption; private _onFocus: boolean; @@ -37,7 +41,7 @@ abstract class NodeGraph { private _elem2d: ElementClass; - constructor(nodeModel: NodeModel, options) { + constructor(nodeModel: NodeModel, options: NodeOption) { $assert(nodeModel, 'model can not be null'); this._options = options; diff --git a/packages/mindplot/src/components/ShrinkConnector.ts b/packages/mindplot/src/components/ShrinkConnector.ts index 7d3139e5..c57acbdd 100644 --- a/packages/mindplot/src/components/ShrinkConnector.ts +++ b/packages/mindplot/src/components/ShrinkConnector.ts @@ -31,8 +31,8 @@ class ShirinkConnector { const ellipse = new Elipse(TopicConfig.INNER_RECT_ATTRIBUTES); this._ellipse = ellipse; - const fillColor = topic.getConnectionColor(); - ellipse.setFill(fillColor); + const color = topic.getConnectionColor(); + ellipse.setFill(color); ellipse.setSize(TopicConfig.CONNECTOR_WIDTH, TopicConfig.CONNECTOR_WIDTH); ellipse.addEvent('click', (event: Event) => { diff --git a/packages/mindplot/src/components/Topic.ts b/packages/mindplot/src/components/Topic.ts index 9a616f27..0f3935a3 100644 --- a/packages/mindplot/src/components/Topic.ts +++ b/packages/mindplot/src/components/Topic.ts @@ -17,9 +17,9 @@ */ import { $assert, $defined } from '@wisemapping/core-js'; -import { Rect, Image, Line, Text, Group, ElementClass, Point } from '@wisemapping/web2d'; +import { Rect, Line, Text, Group, ElementClass } from '@wisemapping/web2d'; -import NodeGraph from './NodeGraph'; +import NodeGraph, { NodeOption } from './NodeGraph'; import TopicConfig from './TopicConfig'; import TopicStyle from './TopicStyle'; import TopicFeatureFactory from './TopicFeature'; @@ -40,6 +40,9 @@ import LinkModel from './model/LinkModel'; import SizeType from './SizeType'; import FeatureModel from './model/FeatureModel'; import ImageIcon from './ImageIcon'; +import PositionType from './PositionType'; +import LineTopicShape from './widget/LineTopicShape'; +import ImageTopicShape from './widget/ImageTopicShape'; const ICON_SCALING_FACTOR = 1.3; @@ -66,7 +69,7 @@ abstract class Topic extends NodeGraph { private _outgoingLine: ConnectionLine | null; - constructor(model: NodeModel, options) { + constructor(model: NodeModel, options: NodeOption) { super(model, options); this._children = []; this._parent = null; @@ -179,12 +182,18 @@ abstract class Topic extends NodeGraph { // Style is infered looking recursivelly on the parent nodes. if (!result) { const parent = this.getParent(); - if (parent) { - result = parent.getConnectionColor(); + if (parent && parent.isCentralTopic()) { + // This means that this is central main node, in this case, I will overwrite with the main color if it was defined. + result = this.getModel().getConnectionColor() || parent.getModel().getConnectionColor(); } else { - result = TopicStyle.defaultConnectionColor(this); + result = parent?.getConnectionColor(); } } + + if (!result) { + result = TopicStyle.defaultConnectionColor(this); + } + return result!; } @@ -219,74 +228,34 @@ abstract class Topic extends NodeGraph { return this._innerShape; } - protected _buildShape(attributes, shapeType: TopicShapeType): ElementClass { - $assert(attributes, 'attributes can not be null'); - $assert(shapeType, 'shapeType can not be null'); - + protected _buildShape(attributes: object, shapeType: TopicShapeType): ElementClass { let result: ElementClass; - if (shapeType === 'rectangle') { - result = new Rect(0, attributes); - } else if (shapeType === 'image') { - const model = this.getModel(); - const url = model.getImageUrl(); - const size = model.getImageSize(); - - result = new Image(); - result.setHref(url); - result.setSize(size.width, size.height); - - result.getSize = function getSize() { - return model.getImageSize(); - }; - - result.setPosition = function setPosition() { - // Ignore ... - }; - } else if (shapeType === 'elipse') { - result = new Rect(0.9, attributes); - } else if (shapeType === 'rounded rectangle') { - result = new Rect(0.3, attributes); - } else if (shapeType === 'line') { - const stokeColor = this.getConnectionColor(); - result = new Line({ - strokeColor: stokeColor, - strokeWidth: 1, - }); - - const me = this; - result.setSize = function setSize(width: number, height: number) { - this.size = { width, height }; - result.setFrom(0, height); - result.setTo(width, height); - - // // Lines will have the same color of the default connection lines... - const color = me.getConnectionColor(); - result.setStroke(1, 'solid', color); - }; - - result.getSize = function getSize() { - return this.size; - }; - - result.setPosition = () => { - // Overwrite behaviour ... - }; - - result.setFill = () => { - // Overwrite behaviour ... - }; - - result.setStroke = () => { - // Overwrite behaviour ... - }; - } else { - $assert(false, `Unsupported figure shapeType:${shapeType}`); + switch (shapeType) { + case 'rectangle': + result = new Rect(0, attributes); + break; + case 'elipse': + result = new Rect(0.9, attributes); + break; + case 'rounded rectangle': + result = new Rect(0.3, attributes); + break; + case 'line': + result = new LineTopicShape(this); + break; + case 'image': + result = new ImageTopicShape(this); + break; + default: { + const exhaustiveCheck: never = shapeType; + throw new Error(exhaustiveCheck); + } } result.setPosition(0, 0); return result; } - setCursor(type: string) { + setCursor(type: string): void { const innerShape = this.getInnerShape(); innerShape.setCursor(type); @@ -583,6 +552,9 @@ abstract class Topic extends NodeGraph { const model = this.getModel(); model.setConnectionColor(value); + // Force redraw for changing line color ... + this.redraw(); + // Needs to change change all the lines color. Outgoing are part of the children. this.getChildren().forEach((topic: Topic) => topic.redraw()); @@ -738,7 +710,7 @@ abstract class Topic extends NodeGraph { return result; } - setChildrenShrunken(value: boolean) { + setChildrenShrunken(value: boolean): void { // Update Model ... const model = this.getModel(); model.setChildrenShrunken(value); @@ -860,7 +832,7 @@ abstract class Topic extends NodeGraph { /** * Point: references the center of the rect shape.!!! */ - setPosition(point: Point) { + setPosition(point: PositionType): void { $assert(point, 'position can not be null'); // allowed param reassign to avoid risks of existing code relying in this side-effect // eslint-disable-next-line no-param-reassign @@ -1306,6 +1278,7 @@ abstract class Topic extends NodeGraph { this._outgoingLine.redraw(); this._connector.setFill(color); this.getChildren().forEach((t) => t.redraw()); + result = true; } } } @@ -1341,6 +1314,17 @@ abstract class Topic extends NodeGraph { const yPosition = Math.round((topicHeight - textHeight) / 2); iconGroup.setPosition(padding, yPosition); textShape.setPosition(padding + iconGroupWith + textIconSpacing, yPosition); + + // Has color changed ? + if (this.getShapeType() === 'line') { + const color = this.getConnectionColor(); + this.getInnerShape().setStroke(1, 'solid', color); + + // Force the repaint in case that the main topic color has changed. + if (this.getParent() && this.getParent()?.isCentralTopic()) { + this._outgoingLine?.redraw(); + } + } } else { // In case of images, the size is fixed ... const size = this.getModel().getImageSize(); @@ -1367,9 +1351,9 @@ abstract class Topic extends NodeGraph { return result; } - abstract workoutOutgoingConnectionPoint(position: Point): Point; + abstract workoutOutgoingConnectionPoint(position: PositionType): PositionType; - abstract workoutIncomingConnectionPoint(position: Point): Point; + abstract workoutIncomingConnectionPoint(position: PositionType): PositionType; isChildTopic(childTopic: Topic): boolean { let result = this.getId() === childTopic.getId(); diff --git a/packages/mindplot/src/components/TopicFactory.ts b/packages/mindplot/src/components/TopicFactory.ts index fb00ac89..9dd37f2e 100644 --- a/packages/mindplot/src/components/TopicFactory.ts +++ b/packages/mindplot/src/components/TopicFactory.ts @@ -3,10 +3,11 @@ import { $assert } from '@wisemapping/core-js'; import CentralTopic from './CentralTopic'; import MainTopic from './MainTopic'; import NodeModel from './model/NodeModel'; +import { NodeOption } from './NodeGraph'; import Topic from './Topic'; class TopicFactory { - static create(nodeModel: NodeModel, options: object): Topic { + static create(nodeModel: NodeModel, options: NodeOption): Topic { $assert(nodeModel, 'Model can not be null'); const type = nodeModel.getType(); diff --git a/packages/mindplot/src/components/model/NodeModel.ts b/packages/mindplot/src/components/model/NodeModel.ts index 8f58f616..70abbf7c 100644 --- a/packages/mindplot/src/components/model/NodeModel.ts +++ b/packages/mindplot/src/components/model/NodeModel.ts @@ -178,16 +178,6 @@ class NodeModel extends INodeModel { if (backgroundColor) { this.setBackgroundColor(backgroundColor); } - - const connectType = value.getConnectionStyle(); - if ($defined(connectType)) { - this.setConnectionStyle(connectType!); - } - - const connectColor = value.getConnectionColor(); - if ($defined(connectColor)) { - this.setConnectionColor(connectColor!); - } } deepCopy(): NodeModel { diff --git a/packages/mindplot/src/components/widget/ImageTopicShape.ts b/packages/mindplot/src/components/widget/ImageTopicShape.ts new file mode 100644 index 00000000..73d2f4f4 --- /dev/null +++ b/packages/mindplot/src/components/widget/ImageTopicShape.ts @@ -0,0 +1,45 @@ +/* + * 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 { Image } from '@wisemapping/web2d'; +import SizeType from '../SizeType'; +import Topic from '../Topic'; + +class ImageTopicShape extends Image { + private _topic: Topic; + + constructor(topic: Topic) { + super(); + const model = topic.getModel(); + const url = model.getImageUrl(); + const size = model.getImageSize(); + + super.setHref(url); + super.setSize(size.width, size.height); + this._topic = topic; + } + + getSize(): SizeType { + return this._topic.getModel().getImageSize(); + } + + setPosition(): void { + // Ignore ... + } +} + +export default ImageTopicShape; diff --git a/packages/mindplot/src/components/widget/LineTopicShape.ts b/packages/mindplot/src/components/widget/LineTopicShape.ts new file mode 100644 index 00000000..213fb63a --- /dev/null +++ b/packages/mindplot/src/components/widget/LineTopicShape.ts @@ -0,0 +1,55 @@ +/* + * 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 { Line } from '@wisemapping/web2d'; +import SizeType from '../SizeType'; +import Topic from '../Topic'; + +class LineTopicShape extends Line { + private _topic: Topic; + + private _size: SizeType; + + constructor(topic: Topic) { + const stokeColor = topic.getConnectionColor(); + super({ + strokeColor: stokeColor, + strokeWidth: 1, + }); + this._topic = topic; + } + + setSize(width: number, height: number): void { + this._size = { width, height }; + super.setFrom(0, height); + super.setTo(width, height); + } + + getSize() { + return this._size; + } + + setPosition() { + // Overwrite behaviour ... + } + + setFill() { + // Overwrite behaviour ... + } +} + +export default LineTopicShape;