Skip to content

Commit

Permalink
Fix Element borders when exporting to SVG (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderG2207 authored Sep 2, 2023
1 parent 5d286be commit a9c5d19
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/main/apollon-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export class ApollonEditor {
_getNewSubscriptionId(subscribers: { [key: number]: any }): number {
// largest key + 1
if (Object.keys(subscribers).length === 0) return 0;
return Math.max(...Object.keys(subscribers).map((key) => parseInt(key))) + 1;
return Math.max(...Object.keys(subscribers).map((key) => parseInt(key))) + 1; // tslint:disable-line
}

/**
Expand Down
66 changes: 53 additions & 13 deletions src/main/scenes/svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { Style } from './svg-styles';
import { StoreProvider } from '../components/store/model-store';
import { ModelState } from '../components/store/model-state';
import { ThemeProvider } from 'styled-components';
import { UMLClassifierComponent } from '../packages/common/uml-classifier/uml-classifier-component';
import { UMLClassifierMemberComponent } from '../packages/common/uml-classifier/uml-classifier-member-component';

type Props = {
model: Apollon.UMLModel;
Expand Down Expand Up @@ -197,6 +199,17 @@ export class Svg extends Component<Props, State> {
return { minX: Math.min(minX, 0), minY: Math.min(minY, 0) };
};

const svgElementDetails = (element: UMLElement, x: number, y: number) => {
return {
x: (x),
y: (y),
width: (element.bounds.width),
height: (element.bounds.height),
className: (element.name ? element.name.replace(/[<>]/g, '') : ''),
fill: (element.fillColor || theme.color.background)
};
};

return (
<StoreProvider initialState={state}>
<ThemeProvider theme={theme}>
Expand All @@ -212,19 +225,46 @@ export class Svg extends Component<Props, State> {
</defs>
{elements.map((element, index) => {
const ElementComponent = Components[element.type as UMLElementType | UMLRelationshipType];
return (
<svg
x={element.bounds.x - translationFactor().minX}
y={element.bounds.y - translationFactor().minY}
width={element.bounds.width}
height={element.bounds.height}
key={element.id}
className={element.name ? element.name.replace(/[<>]/g, '') : ''}
fill={element.fillColor || theme.color.background}
>
<ElementComponent key={index} element={element} scale={this.props.options?.scale || 1.0} />
</svg>
);
switch (ElementComponent) {
case UMLClassifierComponent:
// If the ElementComponent is of type UMLClassifierComponent, create an array of all members (attributes and methods) for that component.
// Unlike other components, the UMLClassifierComponent needs its members to be children within the component to avoid border rendering issues.
const members = elements.filter((member) => member.owner === element.id);
return (
<svg
key={element.id}
{...svgElementDetails(element, element.bounds.x, element.bounds.y)}
>
<ElementComponent key={index} element={element} scale={this.props.options?.scale || 1.0}>
{members.map((memberElement, memberIndex) => {
// Nest the members within the UMLClassifierComponent so the border rectangle and path get rendered afterward.
const MemberElementComponent = Components[memberElement.type as UMLElementType];
return (
<svg
key={memberElement.id}
{...svgElementDetails(memberElement, 0, memberElement.bounds.y - element.bounds.y)}
>
<MemberElementComponent key={memberIndex} element={memberElement} scale={this.props.options?.scale || 1.0} />
</svg>
);
})}
</ElementComponent>
</svg>
);
case UMLClassifierMemberComponent:
// If the ElementComponent is of type UMLClassifierMemberComponent, we break out of the switch, as they have been rendered within the UMLClassifierComponent.
break;
default:
// Render all other UMLElements and UMLRelationships normally, as they don't have issues when rendering to SVG.
return (
<svg
key={element.id}
{...svgElementDetails(element, element.bounds.x - translationFactor().minX, element.bounds.y - translationFactor().minY)}
>
<ElementComponent key={index} element={element} scale={this.props.options?.scale || 1.0} />
</svg>
);
}
})}
</svg>
</ThemeProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/tests/unit/test-resources/class-diagram-as-svg.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"<svg width=\"851\" height=\"251\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" fill=\"#ffffff\"><defs><style>\n text {\n fill: #212529;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n font-size: 16px;\n }\n\n marker, text {\n fill-opacity: 1;\n }\n\n * {\n overflow: visible;\n }\n</style></defs><svg x=\"15\" y=\"15\" width=\"350\" height=\"220\" class=\"Package\" fill=\"#ffffff\"><g><path style=\"transform: scale(1);\" d=\"M 0 10 V 0 H 40 V 10\" stroke=\"black\" fill=\"white\" ></path><rect y=\"10\" width=\"100%\" height=\"210\" stroke=\"black\" fill=\"white\" ></rect><text x=\"50%\" y=\"20\" dy=\"10\" text-anchor=\"middle\" font-weight=\"bold\" pointer-events=\"none\">Package</text></g></svg><svg x=\"95\" y=\"85\" width=\"200\" height=\"100\" class=\"Class\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"40\" stroke=\"none\" fill=\"white\" ></rect><rect y=\"40\" width=\"100%\" height=\"60\" stroke=\"none\" fill=\"white\" ></rect><svg height=\"40\"><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-weight=\"bold\" pointer-events=\"none\">Class</text></svg><rect width=\"100%\" height=\"100%\" stroke=\"black\" fill=\"none\" ></rect><path d=\"M 0 40 H 200\" stroke=\"black\" fill=\"white\" ></path><path d=\"M 0 70 H 200\" stroke=\"black\" fill=\"white\" ></path></g></svg><svg x=\"95\" y=\"125\" width=\"199\" height=\"30\" class=\"+ attribute: Type\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ attribute: Type</text></g></svg><svg x=\"95\" y=\"155\" width=\"199\" height=\"30\" class=\"+ method()\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ method()</text></g></svg><svg x=\"635\" y=\"105\" width=\"200\" height=\"100\" class=\"Class\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"40\" stroke=\"none\" fill=\"white\" ></rect><rect y=\"40\" width=\"100%\" height=\"60\" stroke=\"none\" fill=\"white\" ></rect><svg height=\"40\"><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-weight=\"bold\" pointer-events=\"none\">Class</text></svg><rect width=\"100%\" height=\"100%\" stroke=\"black\" fill=\"none\" ></rect><path d=\"M 0 40 H 200\" stroke=\"black\" fill=\"white\" ></path><path d=\"M 0 70 H 200\" stroke=\"black\" fill=\"white\" ></path></g></svg><svg x=\"635\" y=\"145\" width=\"199\" height=\"30\" class=\"+ attribute: Type\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ attribute: Type</text></g></svg><svg x=\"635\" y=\"175\" width=\"199\" height=\"30\" class=\"+ method()\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ method()</text></g></svg><svg x=\"295\" y=\"145\" width=\"340\" height=\"1\" class=\"\" fill=\"#ffffff\"><g><polyline points=\"340 0,0 0\" stroke-width=\"1\" marker-end=\"url(#marker-f5c4e20d-8347-4136-bc02-b7a016e017f5)\" stroke=\"black\" fill=\"none\" ></polyline><text x=\"332\" y=\"0\" dy=\"21\" text-anchor=\"end\" pointer-events=\"none\"></text><text x=\"8\" y=\"0\" dy=\"21\" text-anchor=\"start\" pointer-events=\"none\"></text><text x=\"332\" y=\"0\" dy=\"-10\" text-anchor=\"end\" pointer-events=\"none\"></text><text x=\"8\" y=\"0\" dy=\"-10\" text-anchor=\"start\" pointer-events=\"none\"></text></g></svg></svg>"
"<svg width=\"851\" height=\"251\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" fill=\"#ffffff\"><defs><style>\n text {\n fill: #212529;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n font-size: 16px;\n }\n\n marker, text {\n fill-opacity: 1;\n }\n\n * {\n overflow: visible;\n }\n</style></defs><svg x=\"15\" y=\"15\" width=\"350\" height=\"220\" class=\"Package\" fill=\"#ffffff\"><g><path style=\"transform: scale(1);\" d=\"M 0 10 V 0 H 40 V 10\" stroke=\"black\" fill=\"white\" ></path><rect y=\"10\" width=\"100%\" height=\"210\" stroke=\"black\" fill=\"white\" ></rect><text x=\"50%\" y=\"20\" dy=\"10\" text-anchor=\"middle\" font-weight=\"bold\" pointer-events=\"none\">Package</text></g></svg><svg x=\"95\" y=\"85\" width=\"200\" height=\"100\" class=\"Class\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"40\" stroke=\"none\" fill=\"white\" ></rect><rect y=\"40\" width=\"100%\" height=\"60\" stroke=\"none\" fill=\"white\" ></rect><svg height=\"40\"><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-weight=\"bold\" pointer-events=\"none\">Class</text></svg><svg x=\"0\" y=\"40\" width=\"199\" height=\"30\" class=\"+ attribute: Type\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ attribute: Type</text></g></svg><svg x=\"0\" y=\"70\" width=\"199\" height=\"30\" class=\"+ method()\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ method()</text></g></svg><rect width=\"100%\" height=\"100%\" stroke=\"black\" fill=\"none\" ></rect><path d=\"M 0 40 H 200\" stroke=\"black\" fill=\"white\" ></path><path d=\"M 0 70 H 200\" stroke=\"black\" fill=\"white\" ></path></g></svg><svg x=\"635\" y=\"105\" width=\"200\" height=\"100\" class=\"Class\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"40\" stroke=\"none\" fill=\"white\" ></rect><rect y=\"40\" width=\"100%\" height=\"60\" stroke=\"none\" fill=\"white\" ></rect><svg height=\"40\"><text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-weight=\"bold\" pointer-events=\"none\">Class</text></svg><svg x=\"0\" y=\"40\" width=\"199\" height=\"30\" class=\"+ attribute: Type\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ attribute: Type</text></g></svg><svg x=\"0\" y=\"70\" width=\"199\" height=\"30\" class=\"+ method()\" fill=\"#ffffff\"><g><rect width=\"100%\" height=\"100%\" stroke=\"none\" fill=\"white\" ></rect><text x=\"10\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"start\" font-weight=\"normal\" pointer-events=\"none\">+ method()</text></g></svg><rect width=\"100%\" height=\"100%\" stroke=\"black\" fill=\"none\" ></rect><path d=\"M 0 40 H 200\" stroke=\"black\" fill=\"white\" ></path><path d=\"M 0 70 H 200\" stroke=\"black\" fill=\"white\" ></path></g></svg><svg x=\"295\" y=\"145\" width=\"340\" height=\"1\" class=\"\" fill=\"#ffffff\"><g><polyline points=\"340 0,0 0\" stroke-width=\"1\" marker-end=\"url(#marker-f5c4e20d-8347-4136-bc02-b7a016e017f5)\" stroke=\"black\" fill=\"none\" ></polyline><text x=\"332\" y=\"0\" dy=\"21\" text-anchor=\"end\" pointer-events=\"none\"></text><text x=\"8\" y=\"0\" dy=\"21\" text-anchor=\"start\" pointer-events=\"none\"></text><text x=\"332\" y=\"0\" dy=\"-10\" text-anchor=\"end\" pointer-events=\"none\"></text><text x=\"8\" y=\"0\" dy=\"-10\" text-anchor=\"start\" pointer-events=\"none\"></text></g></svg></svg>"

0 comments on commit a9c5d19

Please sign in to comment.