feat: allow to load an XML-stored model produced by `mxGraph` (#300)
Implementation relies on both the declaration of aliases and the introduction of dedicated codecs. `Codec.getCodecByName` now takes aliases into account, so that importing is fully functional. The code of some `CodecRegistry` and `Codec` methods has been simplified (check for nullity of method parameters). The cell style is converted thanks to a specific utility function that can serve as an example for those wishing to migrate their applications from mxGraph to maxGraph. test - The `Codec` example has been restored from the stashed directory. It is now available as a story written in TypeScript. - The `ModelChecker` utility class was previously used only to validate the imported model after import from the maxGraph model. It is now shared and also used in mxGraph import tests. It has also been enhanced to check the total number of cells in the model.development
parent
41a3fbfd55
commit
835bfe7ce9
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 { describe, expect, test } from '@jest/globals';
|
||||||
|
import { convertStyleFromString } from '../../../../src/serialization/codecs/mxGraph/utils';
|
||||||
|
import { CellStyle } from '../../../../src';
|
||||||
|
|
||||||
|
describe('convertStyleFromString', () => {
|
||||||
|
test('Basic', () => {
|
||||||
|
// adapted from https://github.com/maxGraph/maxGraph/issues/221
|
||||||
|
expect(
|
||||||
|
convertStyleFromString(
|
||||||
|
'rounded=0;whiteSpace=wrap;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;fontSize=27'
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
rounded: 0, // FIX should be true
|
||||||
|
whiteSpace: 'wrap',
|
||||||
|
fillColor: '#dae8fc',
|
||||||
|
strokeColor: '#6c8ebf',
|
||||||
|
fontStyle: 1,
|
||||||
|
fontSize: 27,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('With leading ;', () => {
|
||||||
|
// To update when implementing https://github.com/maxGraph/maxGraph/issues/154
|
||||||
|
expect(convertStyleFromString(';arcSize=4;endSize=5;')).toEqual({
|
||||||
|
arcSize: 4,
|
||||||
|
endSize: 5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('With trailing ;', () => {
|
||||||
|
// from https://github.com/maxGraph/maxGraph/issues/102#issuecomment-1225577772
|
||||||
|
expect(
|
||||||
|
convertStyleFromString(
|
||||||
|
'rounded=0;whiteSpace=wrap;html=1;fillColor=#E6E6E6;dashed=1;'
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
rounded: 0, // FIX should be true
|
||||||
|
whiteSpace: 'wrap',
|
||||||
|
html: 1, // custom draw.io
|
||||||
|
fillColor: '#E6E6E6',
|
||||||
|
dashed: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// manage base name style (no = at the begining and in the middle)
|
||||||
|
test('With base name style', () => {
|
||||||
|
expect(
|
||||||
|
convertStyleFromString(
|
||||||
|
'rectangle;fontColor=yellow;customRectangle;gradientColor=white;'
|
||||||
|
)
|
||||||
|
).toEqual(<CellStyle>{
|
||||||
|
baseStyleNames: ['rectangle', 'customRectangle'],
|
||||||
|
fontColor: 'yellow',
|
||||||
|
gradientColor: 'white',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// renamed properties (see migration guide)
|
||||||
|
test('With renamed properties', () => {
|
||||||
|
// @ts-ignore
|
||||||
|
expect(convertStyleFromString('autosize=1')).toEqual(<CellStyle>{
|
||||||
|
autoSize: 1, // FIX should be true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 { describe, test } from '@jest/globals';
|
||||||
|
import { ModelChecker } from './utils';
|
||||||
|
import {
|
||||||
|
CodecRegistry,
|
||||||
|
Geometry,
|
||||||
|
GraphDataModel,
|
||||||
|
ModelXmlSerializer,
|
||||||
|
Point,
|
||||||
|
} from '../../src';
|
||||||
|
|
||||||
|
describe('import mxGraph model', () => {
|
||||||
|
test('Model with geometry', () => {
|
||||||
|
const mxGraphModelAsXml = `<mxGraphModel>
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" vertex="1" parent="1" value="Vertex #2">
|
||||||
|
<mxGeometry x="380" y="20" width="140" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" vertex="1" parent="1" value="Vertex #3">
|
||||||
|
<mxGeometry x="200" y="80" width="380" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" edge="1" source="2" target="3" parent="1" value="Edge #7">
|
||||||
|
<mxGeometry as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<Object x="420" y="60"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const model = new GraphDataModel();
|
||||||
|
new ModelXmlSerializer(model).import(mxGraphModelAsXml);
|
||||||
|
|
||||||
|
const modelChecker = new ModelChecker(model);
|
||||||
|
|
||||||
|
modelChecker.checkRootCells();
|
||||||
|
modelChecker.checkCellsCount(5);
|
||||||
|
|
||||||
|
modelChecker.expectIsVertex(model.getCell('2'), 'Vertex #2', {
|
||||||
|
geometry: new Geometry(380, 20, 140, 30),
|
||||||
|
});
|
||||||
|
|
||||||
|
modelChecker.expectIsVertex(model.getCell('3'), 'Vertex #3', {
|
||||||
|
geometry: new Geometry(200, 80, 380, 30),
|
||||||
|
});
|
||||||
|
|
||||||
|
const edgeGeometry = new Geometry();
|
||||||
|
edgeGeometry.points = [new Point(420, 60)];
|
||||||
|
modelChecker.expectIsEdge(model.getCell('7'), 'Edge #7', {
|
||||||
|
geometry: edgeGeometry,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Model with style', () => {
|
||||||
|
const xmlWithStyleAttribute = `<mxGraphModel>
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" vertex="1" parent="1" value="Vertex with style" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#E6E6E6;dashed=1;">
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>`;
|
||||||
|
|
||||||
|
const model = new GraphDataModel();
|
||||||
|
new ModelXmlSerializer(model).import(xmlWithStyleAttribute);
|
||||||
|
|
||||||
|
const modelChecker = new ModelChecker(model);
|
||||||
|
|
||||||
|
modelChecker.checkRootCells();
|
||||||
|
modelChecker.checkCellsCount(3);
|
||||||
|
modelChecker.expectIsVertex(model.getCell('2'), 'Vertex with style', {
|
||||||
|
style: {
|
||||||
|
// @ts-ignore FIX should be true
|
||||||
|
dashed: 1,
|
||||||
|
fillColor: '#E6E6E6',
|
||||||
|
html: 1,
|
||||||
|
// @ts-ignore FIX should be false
|
||||||
|
rounded: 0,
|
||||||
|
whiteSpace: 'wrap',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('import model from draw.io', () => {
|
||||||
|
test('from https://github.com/maxGraph/maxGraph/issues/221', () => {
|
||||||
|
const model = new GraphDataModel();
|
||||||
|
new ModelXmlSerializer(model)
|
||||||
|
.import(`<mxGraphModel dx="1502" dy="926" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1"
|
||||||
|
pageScale="1" pageWidth="1920" pageHeight="1200" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2Ija7sB8CSz23ppRtK8d-5" value="PostgreSQL"
|
||||||
|
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;fontSize=27;"
|
||||||
|
parent="1" vertex="1">
|
||||||
|
<mxGeometry x="650" y="430" width="210" height="80" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>`);
|
||||||
|
|
||||||
|
const modelChecker = new ModelChecker(model);
|
||||||
|
modelChecker.checkRootCells();
|
||||||
|
modelChecker.checkCellsCount(3);
|
||||||
|
modelChecker.expectIsVertex(model.getCell('2Ija7sB8CSz23ppRtK8d-5'), 'PostgreSQL', {
|
||||||
|
geometry: new Geometry(650, 430, 210, 80),
|
||||||
|
style: {
|
||||||
|
fillColor: '#dae8fc',
|
||||||
|
fontSize: 27,
|
||||||
|
fontStyle: 1,
|
||||||
|
html: 1,
|
||||||
|
// @ts-ignore FIX should be false
|
||||||
|
rounded: 0,
|
||||||
|
strokeColor: '#6c8ebf',
|
||||||
|
whiteSpace: 'wrap',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,15 +15,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, expect, test } from '@jest/globals';
|
import { describe, expect, test } from '@jest/globals';
|
||||||
|
import { ModelChecker } from './utils';
|
||||||
import { createGraphWithoutContainer } from '../utils';
|
import { createGraphWithoutContainer } from '../utils';
|
||||||
import {
|
import { Cell, Geometry, GraphDataModel, ModelXmlSerializer, Point } from '../../src';
|
||||||
Cell,
|
|
||||||
type CellStyle,
|
|
||||||
Geometry,
|
|
||||||
GraphDataModel,
|
|
||||||
ModelXmlSerializer,
|
|
||||||
Point,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
// inspired by VertexMixin.createVertex
|
// inspired by VertexMixin.createVertex
|
||||||
const newVertex = (id: string, value: string) => {
|
const newVertex = (id: string, value: string) => {
|
||||||
|
@ -47,68 +41,6 @@ const getParent = (model: GraphDataModel) => {
|
||||||
return model.getRoot()!.getChildAt(0);
|
return model.getRoot()!.getChildAt(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExpectCellProperties = {
|
|
||||||
geometry?: Geometry;
|
|
||||||
style?: CellStyle;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to check the model after import.
|
|
||||||
*/
|
|
||||||
class ModelChecker {
|
|
||||||
constructor(private model: GraphDataModel) {}
|
|
||||||
|
|
||||||
checkRootCells() {
|
|
||||||
const cell0 = this.model.getCell('0');
|
|
||||||
expect(cell0).toBeDefined();
|
|
||||||
expect(cell0).not.toBeNull();
|
|
||||||
expect(cell0?.parent).toBeNull();
|
|
||||||
|
|
||||||
const cell1 = this.model.getCell('1');
|
|
||||||
expect(cell1).toBeDefined();
|
|
||||||
expect(cell1).not.toBeNull();
|
|
||||||
expect(cell1?.parent).toBe(cell0);
|
|
||||||
}
|
|
||||||
|
|
||||||
expectIsVertex(cell: Cell | null, value: string, properties?: ExpectCellProperties) {
|
|
||||||
this.checkCellBaseProperties(cell, value, properties);
|
|
||||||
if (!cell) return; // cannot occur, this is enforced by checkCellBaseProperties
|
|
||||||
expect(cell.edge).toEqual(false);
|
|
||||||
expect(cell.isEdge()).toBeFalsy();
|
|
||||||
expect(cell.vertex).toEqual(1); // FIX should be set to true
|
|
||||||
expect(cell.isVertex()).toBeTruthy();
|
|
||||||
}
|
|
||||||
|
|
||||||
expectIsEdge(
|
|
||||||
cell: Cell | null,
|
|
||||||
value: string | null = null,
|
|
||||||
properties?: ExpectCellProperties
|
|
||||||
) {
|
|
||||||
this.checkCellBaseProperties(cell, value, properties);
|
|
||||||
if (!cell) return; // cannot occur, this is enforced by checkCellBaseProperties
|
|
||||||
expect(cell.edge).toEqual(1); // FIX should be set to true
|
|
||||||
expect(cell.isEdge()).toBeTruthy();
|
|
||||||
expect(cell.vertex).toEqual(false);
|
|
||||||
expect(cell.isVertex()).toBeFalsy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkCellBaseProperties(
|
|
||||||
cell: Cell | null,
|
|
||||||
value: string | null,
|
|
||||||
properties?: ExpectCellProperties
|
|
||||||
) {
|
|
||||||
expect(cell).toBeDefined();
|
|
||||||
expect(cell).not.toBeNull();
|
|
||||||
if (!cell) return; // cannot occur, see above
|
|
||||||
|
|
||||||
expect(cell.value).toEqual(value);
|
|
||||||
expect(cell.getParent()?.id).toEqual('1'); // default parent id
|
|
||||||
|
|
||||||
expect(cell.geometry).toEqual(properties?.geometry ?? null);
|
|
||||||
expect(cell.style).toEqual(properties?.style ?? {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adapted from https://github.com/maxGraph/maxGraph/issues/178
|
// Adapted from https://github.com/maxGraph/maxGraph/issues/178
|
||||||
const xmlWithSingleVertex = `<GraphDataModel>
|
const xmlWithSingleVertex = `<GraphDataModel>
|
||||||
<root>
|
<root>
|
||||||
|
@ -201,6 +133,7 @@ describe('import before the export (reproduce https://github.com/maxGraph/maxGra
|
||||||
|
|
||||||
const modelChecker = new ModelChecker(model);
|
const modelChecker = new ModelChecker(model);
|
||||||
modelChecker.checkRootCells();
|
modelChecker.checkRootCells();
|
||||||
|
modelChecker.checkCellsCount(3);
|
||||||
|
|
||||||
modelChecker.expectIsVertex(model.getCell('B_#0'), 'rootNode', {
|
modelChecker.expectIsVertex(model.getCell('B_#0'), 'rootNode', {
|
||||||
geometry: new Geometry(100, 100, 100, 80),
|
geometry: new Geometry(100, 100, 100, 80),
|
||||||
|
@ -305,6 +238,7 @@ describe('import after export', () => {
|
||||||
|
|
||||||
const modelChecker = new ModelChecker(model);
|
const modelChecker = new ModelChecker(model);
|
||||||
modelChecker.checkRootCells();
|
modelChecker.checkRootCells();
|
||||||
|
modelChecker.checkCellsCount(3);
|
||||||
|
|
||||||
modelChecker.expectIsVertex(model.getCell('B_#0'), 'rootNode', {
|
modelChecker.expectIsVertex(model.getCell('B_#0'), 'rootNode', {
|
||||||
geometry: new Geometry(100, 100, 100, 80),
|
geometry: new Geometry(100, 100, 100, 80),
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 { expect } from '@jest/globals';
|
||||||
|
import type { Cell, CellStyle, Geometry, GraphDataModel } from '../../src';
|
||||||
|
|
||||||
|
type ExpectCellProperties = {
|
||||||
|
geometry?: Geometry;
|
||||||
|
style?: CellStyle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to check the content of GraphDataModel.
|
||||||
|
*/
|
||||||
|
export class ModelChecker {
|
||||||
|
constructor(private model: GraphDataModel) {}
|
||||||
|
|
||||||
|
checkRootCells() {
|
||||||
|
const cell0 = this.model.getCell('0');
|
||||||
|
expect(cell0).toBeDefined();
|
||||||
|
expect(cell0).not.toBeNull();
|
||||||
|
expect(cell0?.parent).toBeNull();
|
||||||
|
|
||||||
|
const cell1 = this.model.getCell('1');
|
||||||
|
expect(cell1).toBeDefined();
|
||||||
|
expect(cell1).not.toBeNull();
|
||||||
|
expect(cell1?.parent).toBe(cell0);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCellsCount(count: number) {
|
||||||
|
const cellIds = Object.getOwnPropertyNames(this.model.cells);
|
||||||
|
expect(cellIds).toHaveLength(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
expectIsVertex(cell: Cell | null, value: string, properties?: ExpectCellProperties) {
|
||||||
|
this.checkCellBaseProperties(cell, value, properties);
|
||||||
|
if (!cell) return; // cannot occur, this is enforced by checkCellBaseProperties
|
||||||
|
expect(cell.edge).toEqual(false);
|
||||||
|
expect(cell.isEdge()).toBeFalsy();
|
||||||
|
expect(cell.vertex).toEqual(1); // FIX should be set to true
|
||||||
|
expect(cell.isVertex()).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
expectIsEdge(
|
||||||
|
cell: Cell | null,
|
||||||
|
value: string | null = null,
|
||||||
|
properties?: ExpectCellProperties
|
||||||
|
) {
|
||||||
|
this.checkCellBaseProperties(cell, value, properties);
|
||||||
|
if (!cell) return; // cannot occur, this is enforced by checkCellBaseProperties
|
||||||
|
expect(cell.edge).toEqual(1); // FIX should be set to true
|
||||||
|
expect(cell.isEdge()).toBeTruthy();
|
||||||
|
expect(cell.vertex).toEqual(false);
|
||||||
|
expect(cell.isVertex()).toBeFalsy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkCellBaseProperties(
|
||||||
|
cell: Cell | null,
|
||||||
|
value: string | null,
|
||||||
|
properties?: ExpectCellProperties
|
||||||
|
) {
|
||||||
|
expect(cell).toBeDefined();
|
||||||
|
expect(cell).not.toBeNull();
|
||||||
|
if (!cell) return; // cannot occur, see above
|
||||||
|
|
||||||
|
expect(cell.value).toEqual(value);
|
||||||
|
expect(cell.getParent()?.id).toEqual('1'); // default parent id
|
||||||
|
|
||||||
|
expect(cell.geometry).toEqual(properties?.geometry ?? null);
|
||||||
|
expect(cell.style).toEqual(properties?.style ?? {});
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,6 @@ import Cell from '../view/cell/Cell';
|
||||||
import MaxLog from '../gui/MaxLog';
|
import MaxLog from '../gui/MaxLog';
|
||||||
import { getFunctionName } from '../util/StringUtils';
|
import { getFunctionName } from '../util/StringUtils';
|
||||||
import { importNode, isNode } from '../util/domUtils';
|
import { importNode, isNode } from '../util/domUtils';
|
||||||
import ObjectCodec from './ObjectCodec';
|
|
||||||
|
|
||||||
const createXmlDocument = () => {
|
const createXmlDocument = () => {
|
||||||
return document.implementation.createDocument('', '', null);
|
return document.implementation.createDocument('', '', null);
|
||||||
|
@ -415,10 +414,11 @@ class Codec {
|
||||||
* and insertEdge on the parent and terminals, respectively.
|
* and insertEdge on the parent and terminals, respectively.
|
||||||
* Default is `true`.
|
* Default is `true`.
|
||||||
*/
|
*/
|
||||||
decodeCell(node: Element, restoreStructures = true): Cell {
|
decodeCell(node: Element, restoreStructures = true): Cell | null {
|
||||||
let cell = null;
|
if (node?.nodeType !== NODETYPE.ELEMENT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (node != null && node.nodeType === NODETYPE.ELEMENT) {
|
|
||||||
// Tries to find a codec for the given node name. If that does
|
// Tries to find a codec for the given node name. If that does
|
||||||
// not return a codec then the node is the user object (an XML node
|
// not return a codec then the node is the user object (an XML node
|
||||||
// that contains the mxCell, aka inversion).
|
// that contains the mxCell, aka inversion).
|
||||||
|
@ -439,12 +439,11 @@ class Codec {
|
||||||
if (!this.isCellCodec(decoder)) {
|
if (!this.isCellCodec(decoder)) {
|
||||||
decoder = CodecRegistry.getCodec(Cell);
|
decoder = CodecRegistry.getCodec(Cell);
|
||||||
}
|
}
|
||||||
cell = decoder?.decode(this, node);
|
const cell = decoder?.decode(this, node);
|
||||||
|
|
||||||
if (restoreStructures) {
|
if (restoreStructures) {
|
||||||
this.insertIntoGraph(cell);
|
this.insertIntoGraph(cell);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,17 +54,18 @@ class CodecRegistry {
|
||||||
static aliases: { [key: string]: string | undefined } = {};
|
static aliases: { [key: string]: string | undefined } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new codec and associates the name of the template constructor in the codec with the codec object.
|
* Registers a new codec and associates the name of the codec via {@link ObjectCodec.getName} with the codec object.
|
||||||
*
|
*
|
||||||
* @param codec ObjectCodec to be registered.
|
* @param codec ObjectCodec to be registered.
|
||||||
|
* @param registerAlias if `true`, register an alias if the codec name doesn't match the name of the constructor of {@link ObjectCodec.template}.
|
||||||
*/
|
*/
|
||||||
static register(codec: ObjectCodec): ObjectCodec {
|
static register(codec: ObjectCodec, registerAlias = true): ObjectCodec {
|
||||||
if (codec != null) {
|
if (codec != null) {
|
||||||
const name = codec.getName();
|
const name = codec.getName();
|
||||||
CodecRegistry.codecs[name] = codec;
|
CodecRegistry.codecs[name] = codec;
|
||||||
|
|
||||||
const classname: string = codec.template.constructor.name;
|
const classname: string = codec.template.constructor.name;
|
||||||
if (classname !== name) {
|
if (registerAlias && classname !== name) {
|
||||||
CodecRegistry.addAlias(classname, name);
|
CodecRegistry.addAlias(classname, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,17 +80,26 @@ class CodecRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a codec that handles objects that are constructed using the given constructor.
|
* Returns a codec that handles objects that are constructed using the given constructor or a codec registered under the provided name.
|
||||||
*
|
*
|
||||||
* @param constructor_ JavaScript constructor function.
|
* When passing a name, the method first check if an alias exists for the name, and if so, it uses it to retrieve the codec.
|
||||||
|
*
|
||||||
|
* If there is no registered Codec, the method tries to register a new Codec using the provided constructor.
|
||||||
|
*
|
||||||
|
* @param constructorOrName JavaScript constructor function of the Codec or Codec name.
|
||||||
*/
|
*/
|
||||||
static getCodec(constructor_: any): ObjectCodec | null {
|
static getCodec(constructorOrName: any): ObjectCodec | null {
|
||||||
|
if (constructorOrName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let codec = null;
|
let codec = null;
|
||||||
|
|
||||||
if (constructor_ != null) {
|
// Equivalent of calling import { getFunctionName } from '../util/StringUtils';
|
||||||
let { name } = constructor_;
|
let name =
|
||||||
const tmp = CodecRegistry.aliases[name];
|
typeof constructorOrName === 'string' ? constructorOrName : constructorOrName.name;
|
||||||
|
|
||||||
|
const tmp = CodecRegistry.aliases[name];
|
||||||
if (tmp != null) {
|
if (tmp != null) {
|
||||||
name = tmp;
|
name = tmp;
|
||||||
}
|
}
|
||||||
|
@ -99,18 +109,30 @@ class CodecRegistry {
|
||||||
// Registers a new default codec for the given constructor if no codec has been previously defined.
|
// Registers a new default codec for the given constructor if no codec has been previously defined.
|
||||||
if (codec == null) {
|
if (codec == null) {
|
||||||
try {
|
try {
|
||||||
codec = new ObjectCodec(new constructor_());
|
codec = new ObjectCodec(new constructorOrName());
|
||||||
CodecRegistry.register(codec);
|
CodecRegistry.register(codec);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First try to get the codec by the name it is registered with. If it doesn't exist, use the alias eventually declared
|
||||||
|
* to get the codec.
|
||||||
|
* @param name the name of the codec that is willing to be retrieved.
|
||||||
|
*/
|
||||||
static getCodecByName(name: string) {
|
static getCodecByName(name: string) {
|
||||||
return CodecRegistry.codecs[name] ?? null;
|
let codec = CodecRegistry.codecs[name];
|
||||||
|
if (!codec) {
|
||||||
|
const alias = CodecRegistry.aliases[name];
|
||||||
|
if (alias) {
|
||||||
|
codec = CodecRegistry.codecs[alias];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codec ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -765,7 +765,6 @@ class ObjectCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isExcluded(obj, name, value, false)) {
|
if (!this.isExcluded(obj, name, value, false)) {
|
||||||
// MaxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
|
|
||||||
obj[name] = value;
|
obj[name] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -858,7 +857,6 @@ class ObjectCodec {
|
||||||
} else {
|
} else {
|
||||||
obj.push(value);
|
obj.push(value);
|
||||||
}
|
}
|
||||||
// MaxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ export class ModelCodec extends ObjectCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes the given {@link GraphDataModel} by writing a (flat) XML sequence of cell nodes as produced by the <CellCodec>.
|
* Encodes the given {@link GraphDataModel} by writing a (flat) XML sequence of cell nodes as produced by the {@link CellCodec}.
|
||||||
* The sequence is wrapped-up in a node with the name root.
|
* The sequence is wrapped-up in a node with the name `root`.
|
||||||
*/
|
*/
|
||||||
encodeObject(enc: any, obj: Cell, node: Element) {
|
encodeObject(enc: any, obj: Cell, node: Element) {
|
||||||
const rootNode = enc.document.createElement('root');
|
const rootNode = enc.document.createElement('root');
|
||||||
|
|
|
@ -23,3 +23,5 @@ export * from './ModelCodec';
|
||||||
export * from './RootChangeCodec';
|
export * from './RootChangeCodec';
|
||||||
export * from './StylesheetCodec';
|
export * from './StylesheetCodec';
|
||||||
export * from './TerminalChangeCodec';
|
export * from './TerminalChangeCodec';
|
||||||
|
export * from './mxGraph/mxCellCodec';
|
||||||
|
export * from './mxGraph/mxGeometryCodec';
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 { convertStyleFromString } from './utils';
|
||||||
|
import { CellCodec } from '../CellCodec';
|
||||||
|
import type Codec from '../../Codec';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CellCodec to support the legacy `mxGraph` format.
|
||||||
|
*/
|
||||||
|
export class mxCellCodec extends CellCodec {
|
||||||
|
getName(): string {
|
||||||
|
return 'mxCell';
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeAttribute(dec: Codec, attr: any, obj?: any) {
|
||||||
|
const attributeNodeName = attr.nodeName;
|
||||||
|
if (obj && attributeNodeName == 'style') {
|
||||||
|
obj['style'] = convertStyleFromString(attr.value);
|
||||||
|
} else {
|
||||||
|
super.decodeAttribute(dec, attr, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 type Codec from '../../Codec';
|
||||||
|
import ObjectCodec from '../../ObjectCodec';
|
||||||
|
import Geometry from '../../../view/geometry/Geometry';
|
||||||
|
import Point from '../../../view/geometry/Point';
|
||||||
|
|
||||||
|
export class mxGeometryCodec extends ObjectCodec {
|
||||||
|
getName(): string {
|
||||||
|
return 'mxGeometry';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(new Geometry());
|
||||||
|
}
|
||||||
|
|
||||||
|
afterDecode(dec: Codec, node: Element | null, obj?: any): any {
|
||||||
|
// Convert points to the right form
|
||||||
|
// input: [ { x: 420, y: 60 }, ... ]
|
||||||
|
// output: [ Point { _x: 420, _y: 60 }, ... ]
|
||||||
|
//
|
||||||
|
// In mxGraph XML, the points are modeled as Object, so there is no way to create an alias to do the decoding with a custom Codec.
|
||||||
|
// Then, it is easier to convert the values to Point objects after the whole decoding of the geometry
|
||||||
|
// <Array as="points">
|
||||||
|
// <Object x="420" y="60"/>
|
||||||
|
// </Array>
|
||||||
|
|
||||||
|
const originalPoints = (obj as Geometry).points;
|
||||||
|
if (originalPoints) {
|
||||||
|
const points: Array<Point> = [];
|
||||||
|
for (const pointInput of originalPoints) {
|
||||||
|
const rawPoint = pointInput as { x: number; y: number };
|
||||||
|
points.push(new Point(rawPoint.x, rawPoint.y));
|
||||||
|
}
|
||||||
|
(obj as Geometry).points = points;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 { isNumeric } from '../../../util/mathUtils';
|
||||||
|
import type { CellStyle } from '../../../types';
|
||||||
|
|
||||||
|
// from mxGraph to maxGraph
|
||||||
|
const fieldMapping = new Map<string, string>([['autosize', 'autoSize']]);
|
||||||
|
|
||||||
|
export function convertStyleFromString(input: string) {
|
||||||
|
const style: CellStyle = {};
|
||||||
|
|
||||||
|
const elements = input
|
||||||
|
.split(';')
|
||||||
|
// filter empty key
|
||||||
|
.filter(([k]) => k);
|
||||||
|
for (const element of elements) {
|
||||||
|
if (!element.includes('=')) {
|
||||||
|
!style.baseStyleNames && (style.baseStyleNames = []);
|
||||||
|
style.baseStyleNames.push(element);
|
||||||
|
} else {
|
||||||
|
const [key, value] = element.split('=');
|
||||||
|
// @ts-ignore
|
||||||
|
style[fieldMapping.get(key) ?? key] = convertToNumericIfNeeded(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToNumericIfNeeded(value: string): string | number {
|
||||||
|
// Adapted from ObjectCodec.convertAttributeFromXml
|
||||||
|
if (!isNumeric(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let numericValue = parseFloat(value);
|
||||||
|
|
||||||
|
if (Number.isNaN(numericValue) || !Number.isFinite(numericValue)) {
|
||||||
|
numericValue = 0;
|
||||||
|
}
|
||||||
|
return numericValue;
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ import {
|
||||||
GenericChangeCodec,
|
GenericChangeCodec,
|
||||||
GraphViewCodec,
|
GraphViewCodec,
|
||||||
ModelCodec,
|
ModelCodec,
|
||||||
|
mxCellCodec,
|
||||||
|
mxGeometryCodec,
|
||||||
RootChangeCodec,
|
RootChangeCodec,
|
||||||
StylesheetCodec,
|
StylesheetCodec,
|
||||||
TerminalChangeCodec,
|
TerminalChangeCodec,
|
||||||
|
@ -88,6 +90,11 @@ export const registerCoreCodecs = (force = false) => {
|
||||||
CodecRegistry.register(new ObjectCodec({})); // Object
|
CodecRegistry.register(new ObjectCodec({})); // Object
|
||||||
CodecRegistry.register(new ObjectCodec([])); // Array
|
CodecRegistry.register(new ObjectCodec([])); // Array
|
||||||
|
|
||||||
|
// mxGraph support
|
||||||
|
CodecRegistry.addAlias('mxGraphModel', 'GraphDataModel');
|
||||||
|
CodecRegistry.register(new mxCellCodec(), false);
|
||||||
|
CodecRegistry.register(new mxGeometryCodec(), false);
|
||||||
|
|
||||||
isCoreCodecsRegistered = true;
|
isCoreCodecsRegistered = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024-present The maxGraph project Contributors
|
||||||
|
Copyright (c) 2006-2013, JGraph Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Graph,
|
||||||
|
InternalEvent,
|
||||||
|
ModelXmlSerializer,
|
||||||
|
type PanningHandler,
|
||||||
|
} from '@maxgraph/core';
|
||||||
|
import { globalTypes, globalValues } from './shared/args.js';
|
||||||
|
|
||||||
|
const xmlModel = `<mxGraphModel>
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" vertex="1" parent="1" value="Interval 1">
|
||||||
|
<mxGeometry x="380" y="20" width="140" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" vertex="1" parent="1" value="Interval 2">
|
||||||
|
<mxGeometry x="200" y="80" width="380" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" vertex="1" parent="1" value="Interval 3">
|
||||||
|
<mxGeometry x="40" y="140" width="260" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" vertex="1" parent="1" value="Interval 4">
|
||||||
|
<mxGeometry x="120" y="200" width="240" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" vertex="1" parent="1" value="Interval 5">
|
||||||
|
<mxGeometry x="420" y="260" width="80" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" edge="1" source="2" target="3" parent="1" value="Transfer1">
|
||||||
|
<mxGeometry as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<Object x="420" y="60"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" edge="1" source="2" target="6" parent="1" value="Transfer2">
|
||||||
|
<mxGeometry as="geometry" relative="1" y="0">
|
||||||
|
<Array as="points">
|
||||||
|
<Object x="600" y="60"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" edge="1" source="3" target="4" parent="1" value="Transfer3">
|
||||||
|
<mxGeometry as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<Object x="260" y="120"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" edge="1" source="4" target="5" parent="1" value="Transfer4">
|
||||||
|
<mxGeometry as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<Object x="200" y="180"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" edge="1" source="4" target="6" parent="1" value="Transfer5">
|
||||||
|
<mxGeometry as="geometry" relative="1" y="-10">
|
||||||
|
<Array as="points">
|
||||||
|
<Object x="460" y="155"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Misc/CodecImport_mxGraph',
|
||||||
|
argTypes: {
|
||||||
|
...globalTypes,
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
...globalValues,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// This example demonstrates dynamically creating a graph from XML coming from mxGraph, as well as
|
||||||
|
// changing the default style for edges in-place.
|
||||||
|
const Template = ({ label, ...args }: Record<string, any>) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.position = 'relative';
|
||||||
|
container.style.overflow = 'hidden';
|
||||||
|
container.style.width = `${args.width}px`;
|
||||||
|
container.style.height = `${args.height}px`;
|
||||||
|
container.style.background = '#eeeeee';
|
||||||
|
container.style.border = '1px solid gray';
|
||||||
|
div.appendChild(container);
|
||||||
|
|
||||||
|
const graph = new Graph(container);
|
||||||
|
graph.centerZoom = false;
|
||||||
|
graph.setTooltips(false);
|
||||||
|
graph.setEnabled(false);
|
||||||
|
|
||||||
|
// Changes the default style for edges "in-place"
|
||||||
|
const style = graph.getStylesheet().getDefaultEdgeStyle();
|
||||||
|
style.edgeStyle = 'elbowEdgeStyle';
|
||||||
|
|
||||||
|
// Enables panning with left mouse button
|
||||||
|
const panningHandler = graph.getPlugin('PanningHandler') as PanningHandler;
|
||||||
|
panningHandler.useLeftButtonForPanning = true;
|
||||||
|
panningHandler.ignoreCell = true;
|
||||||
|
graph.container.style.cursor = 'move';
|
||||||
|
graph.setPanning(true);
|
||||||
|
InternalEvent.disableContextMenu(container);
|
||||||
|
|
||||||
|
new ModelXmlSerializer(graph.getDataModel()).import(xmlModel);
|
||||||
|
graph.resizeContainer = false;
|
||||||
|
|
||||||
|
// Adds zoom buttons in top, left corner
|
||||||
|
const buttons = document.createElement('div');
|
||||||
|
buttons.style.position = 'absolute';
|
||||||
|
buttons.style.overflow = 'visible';
|
||||||
|
|
||||||
|
const bs = graph.getBorderSizes();
|
||||||
|
buttons.style.top = `${container.offsetTop + bs.y}px`;
|
||||||
|
buttons.style.left = `${container.offsetLeft + bs.x}px`;
|
||||||
|
|
||||||
|
let left = 0;
|
||||||
|
const bw = 16;
|
||||||
|
const bh = 16;
|
||||||
|
|
||||||
|
function addButton(label: string, funct: () => void) {
|
||||||
|
const btn = document.createElement('div');
|
||||||
|
const labelNode = btn.ownerDocument.createTextNode(label);
|
||||||
|
btn.appendChild(labelNode);
|
||||||
|
|
||||||
|
btn.style.position = 'absolute';
|
||||||
|
btn.style.backgroundColor = 'transparent';
|
||||||
|
btn.style.border = '1px solid gray';
|
||||||
|
btn.style.textAlign = 'center';
|
||||||
|
btn.style.fontSize = '10px';
|
||||||
|
btn.style.cursor = 'pointer';
|
||||||
|
btn.style.width = `${bw}px`;
|
||||||
|
btn.style.height = `${bh}px`;
|
||||||
|
btn.style.left = `${left}px`;
|
||||||
|
btn.style.top = '0px';
|
||||||
|
|
||||||
|
InternalEvent.addListener(btn, 'click', function (evt: Event) {
|
||||||
|
funct();
|
||||||
|
InternalEvent.consume(evt);
|
||||||
|
});
|
||||||
|
|
||||||
|
left += bw;
|
||||||
|
|
||||||
|
buttons.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
addButton('+', function () {
|
||||||
|
graph.zoomIn();
|
||||||
|
});
|
||||||
|
|
||||||
|
addButton('-', function () {
|
||||||
|
graph.zoomOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
div.insertBefore(buttons, container);
|
||||||
|
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
|
@ -1,277 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2006-2013, JGraph Ltd
|
|
||||||
*
|
|
||||||
* Codec
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import mxEvent from '../mxgraph/util/mxEvent';
|
|
||||||
import mxGraph from '../mxgraph/view/mxGraph';
|
|
||||||
import Codec from '../mxgraph/io/Codec';
|
|
||||||
import mxUtils from '../mxgraph/util/mxUtils';
|
|
||||||
import mxConstants from '../mxgraph/util/mxConstants';
|
|
||||||
import mxEdgeStyle from '../mxgraph/view/mxEdgeStyle';
|
|
||||||
|
|
||||||
|
|
||||||
// Contains a graph description which will be converted
|
|
||||||
const HTML_TEMPLATE = `
|
|
||||||
<h1>Codec</h1>
|
|
||||||
|
|
||||||
This example demonstrates dynamically creating a graph from XML and
|
|
||||||
encoding the model into XML, as well as changing the default style for
|
|
||||||
edges in-place. This graph is embedded in the page.
|
|
||||||
<div className="mxgraph" style="position:relative;overflow:auto;">
|
|
||||||
<mxGraphModel><root><mxCell id="0"/><mxCell
|
|
||||||
id="1" parent="0"/><mxCell id="2" vertex="1" parent="1"
|
|
||||||
value="Interval 1"><mxGeometry x="380" y="0" width="140"
|
|
||||||
height="30" as="geometry"/></mxCell><mxCell id="3"
|
|
||||||
vertex="1" parent="1" value="Interval 2"><mxGeometry x="200"
|
|
||||||
y="80" width="380" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="4" vertex="1"
|
|
||||||
parent="1" value="Interval 3"><mxGeometry x="40" y="140"
|
|
||||||
width="260" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="5" vertex="1" parent="1" value="Interval 4"><mxGeometry
|
|
||||||
x="120" y="200" width="240" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="6" vertex="1"
|
|
||||||
parent="1" value="Interval 5"><mxGeometry x="420" y="260"
|
|
||||||
width="80" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="7" edge="1" source="2" target="3" parent="1"
|
|
||||||
value="Transfer1"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="420"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="8" edge="1" source="2" target="6" parent="1"
|
|
||||||
value=""><mxGeometry as="geometry" relative="1"
|
|
||||||
y="-30"><Array as="points"><Object x="600"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="9" edge="1" source="3" target="4" parent="1"
|
|
||||||
value="Transfer3"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="260"
|
|
||||||
y="120"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="10" edge="1" source="4" target="5" parent="1"
|
|
||||||
value="Transfer4"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="200"
|
|
||||||
y="180"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="11" edge="1" source="4" target="6" parent="1"
|
|
||||||
value="Transfer5"><mxGeometry as="geometry" relative="1"
|
|
||||||
y="-10"><Array as="points"><Object x="460"
|
|
||||||
y="155"/></Array></mxGeometry></mxCell></root></mxGraphModel>
|
|
||||||
</div>
|
|
||||||
This graph is embedded in the page.
|
|
||||||
<div
|
|
||||||
className="mxgraph"
|
|
||||||
style="position:relative;background:#eeeeee;border:1px solid gray;overflow:auto;width:400px;height:200px;"
|
|
||||||
>
|
|
||||||
<mxGraphModel><root><mxCell id="0"/><mxCell
|
|
||||||
id="1" parent="0"/><mxCell id="2" vertex="1" parent="1"
|
|
||||||
value="Interval 1"><mxGeometry x="380" y="0" width="140"
|
|
||||||
height="30" as="geometry"/></mxCell><mxCell id="3"
|
|
||||||
vertex="1" parent="1" value="Interval 2"><mxGeometry x="200"
|
|
||||||
y="80" width="380" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="4" vertex="1"
|
|
||||||
parent="1" value="Interval 3"><mxGeometry x="40" y="140"
|
|
||||||
width="260" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="5" vertex="1" parent="1" value="Interval 4"><mxGeometry
|
|
||||||
x="120" y="200" width="240" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="6" vertex="1"
|
|
||||||
parent="1" value="Interval 5"><mxGeometry x="420" y="260"
|
|
||||||
width="80" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="7" edge="1" source="2" target="3" parent="1"
|
|
||||||
value="Transfer1"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="420"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="8" edge="1" source="2" target="6" parent="1"
|
|
||||||
value=""><mxGeometry as="geometry" relative="1"
|
|
||||||
y="-30"><Array as="points"><Object x="600"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="9" edge="1" source="3" target="4" parent="1"
|
|
||||||
value="Transfer3"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="260"
|
|
||||||
y="120"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="10" edge="1" source="4" target="5" parent="1"
|
|
||||||
value="Transfer4"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="200"
|
|
||||||
y="180"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="11" edge="1" source="4" target="6" parent="1"
|
|
||||||
value="Transfer5"><mxGeometry as="geometry" relative="1"
|
|
||||||
y="-10"><Array as="points"><Object x="460"
|
|
||||||
y="155"/></Array></mxGeometry></mxCell></root></mxGraphModel>
|
|
||||||
</div>
|
|
||||||
This graph is embedded in the page.
|
|
||||||
<div
|
|
||||||
className="mxgraph"
|
|
||||||
style="position:relative;background:#eeeeee;border:6px solid gray;overflow:auto;width:400px;height:200px;"
|
|
||||||
>
|
|
||||||
<mxGraphModel><root><mxCell id="0"/><mxCell
|
|
||||||
id="1" parent="0"/><mxCell id="2" vertex="1" parent="1"
|
|
||||||
value="Interval 1"><mxGeometry x="380" y="20" width="140"
|
|
||||||
height="30" as="geometry"/></mxCell><mxCell id="3"
|
|
||||||
vertex="1" parent="1" value="Interval 2"><mxGeometry x="200"
|
|
||||||
y="80" width="380" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="4" vertex="1"
|
|
||||||
parent="1" value="Interval 3"><mxGeometry x="40" y="140"
|
|
||||||
width="260" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="5" vertex="1" parent="1" value="Interval 4"><mxGeometry
|
|
||||||
x="120" y="200" width="240" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="6" vertex="1"
|
|
||||||
parent="1" value="Interval 5"><mxGeometry x="420" y="260"
|
|
||||||
width="80" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="7" edge="1" source="2" target="3" parent="1"
|
|
||||||
value="Transfer1"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="420"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="8" edge="1" source="2" target="6" parent="1"
|
|
||||||
value="Transfer2"><mxGeometry as="geometry" relative="1"
|
|
||||||
y="0"><Array as="points"><Object x="600"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="9" edge="1" source="3" target="4" parent="1"
|
|
||||||
value="Transfer3"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="260"
|
|
||||||
y="120"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="10" edge="1" source="4" target="5" parent="1"
|
|
||||||
value="Transfer4"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="200"
|
|
||||||
y="180"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="11" edge="1" source="4" target="6" parent="1"
|
|
||||||
value="Transfer5"><mxGeometry as="geometry" relative="1"
|
|
||||||
y="-10"><Array as="points"><Object x="460"
|
|
||||||
y="155"/></Array></mxGeometry></mxCell></root></mxGraphModel>
|
|
||||||
</div>
|
|
||||||
This graph is embedded in the page.
|
|
||||||
<div
|
|
||||||
className="mxgraph"
|
|
||||||
style="position:relative;overflow:hidden;border:6px solid gray;"
|
|
||||||
>
|
|
||||||
<mxGraphModel><root><mxCell id="0"/><mxCell
|
|
||||||
id="1" parent="0"/><mxCell id="2" vertex="1" parent="1"
|
|
||||||
value="Interval 1"><mxGeometry x="380" y="20" width="140"
|
|
||||||
height="30" as="geometry"/></mxCell><mxCell id="3"
|
|
||||||
vertex="1" parent="1" value="Interval 2"><mxGeometry x="200"
|
|
||||||
y="80" width="380" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="4" vertex="1"
|
|
||||||
parent="1" value="Interval 3"><mxGeometry x="40" y="140"
|
|
||||||
width="260" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="5" vertex="1" parent="1" value="Interval 4"><mxGeometry
|
|
||||||
x="120" y="200" width="240" height="30"
|
|
||||||
as="geometry"/></mxCell><mxCell id="6" vertex="1"
|
|
||||||
parent="1" value="Interval 5"><mxGeometry x="420" y="260"
|
|
||||||
width="80" height="30" as="geometry"/></mxCell><mxCell
|
|
||||||
id="7" edge="1" source="2" target="3" parent="1"
|
|
||||||
value="Transfer1"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="420"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="8" edge="1" source="2" target="6" parent="1"
|
|
||||||
value="Transfer2"><mxGeometry as="geometry" relative="1"
|
|
||||||
y="0"><Array as="points"><Object x="600"
|
|
||||||
y="60"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="9" edge="1" source="3" target="4" parent="1"
|
|
||||||
value="Transfer3"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="260"
|
|
||||||
y="120"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="10" edge="1" source="4" target="5" parent="1"
|
|
||||||
value="Transfer4"><mxGeometry as="geometry"><Array
|
|
||||||
as="points"><Object x="200"
|
|
||||||
y="180"/></Array></mxGeometry></mxCell><mxCell
|
|
||||||
id="11" edge="1" source="4" target="6" parent="1"
|
|
||||||
value="Transfer5"><mxGeometry as="geometry" relative="1"
|
|
||||||
y="-10"><Array as="points"><Object x="460"
|
|
||||||
y="155"/></Array></mxGeometry></mxCell></root></mxGraphModel>
|
|
||||||
</div>
|
|
||||||
This graph is embedded in the page.`
|
|
||||||
|
|
||||||
|
|
||||||
const divs = document.getElementsByTagName('*');
|
|
||||||
|
|
||||||
for (let i = 0; i < divs.length; i += 1) {
|
|
||||||
if (divs[i].className.toString().indexOf('mxgraph') >= 0) {
|
|
||||||
(function(container) {
|
|
||||||
const xml = mxUtils.getTextContent(container);
|
|
||||||
const xmlDocument = mxUtils.parseXml(xml);
|
|
||||||
|
|
||||||
if (
|
|
||||||
xmlDocument.documentElement != null &&
|
|
||||||
xmlDocument.documentElement.nodeName === 'mxGraphModel'
|
|
||||||
) {
|
|
||||||
const decoder = new Codec(xmlDocument);
|
|
||||||
const node = xmlDocument.documentElement;
|
|
||||||
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
const graph = new mxGraph(container);
|
|
||||||
graph.centerZoom = false;
|
|
||||||
graph.setTooltips(false);
|
|
||||||
graph.setEnabled(false);
|
|
||||||
|
|
||||||
// Changes the default style for edges "in-place"
|
|
||||||
const style = graph.getStylesheet().getDefaultEdgeStyle();
|
|
||||||
style.edge = mxEdgeStyle.ElbowConnector;
|
|
||||||
|
|
||||||
// Enables panning with left mouse button
|
|
||||||
graph.getPlugin('PanningHandler').useLeftButtonForPanning = true;
|
|
||||||
graph.getPlugin('PanningHandler').ignoreCell = true;
|
|
||||||
graph.container.style.cursor = 'move';
|
|
||||||
graph.setPanning(true);
|
|
||||||
|
|
||||||
if (divs[i].style.width === '' && divs[i].style.height === '') {
|
|
||||||
graph.resizeContainer = true;
|
|
||||||
} else {
|
|
||||||
// Adds border for fixed size boxes
|
|
||||||
graph.border = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder.decode(node, graph.getDataModel());
|
|
||||||
graph.resizeContainer = false;
|
|
||||||
|
|
||||||
// Adds zoom buttons in top, left corner
|
|
||||||
const buttons = document.createElement('div');
|
|
||||||
buttons.style.position = 'absolute';
|
|
||||||
buttons.style.overflow = 'visible';
|
|
||||||
|
|
||||||
const bs = graph.getBorderSizes();
|
|
||||||
buttons.style.top = `${container.offsetTop + bs.y}px`;
|
|
||||||
buttons.style.left = `${container.offsetLeft + bs.x}px`;
|
|
||||||
|
|
||||||
let left = 0;
|
|
||||||
const bw = 16;
|
|
||||||
const bh = 16;
|
|
||||||
|
|
||||||
function addButton(label, funct) {
|
|
||||||
const btn = document.createElement('div');
|
|
||||||
mxUtils.write(btn, label);
|
|
||||||
btn.style.position = 'absolute';
|
|
||||||
btn.style.backgroundColor = 'transparent';
|
|
||||||
btn.style.border = '1px solid gray';
|
|
||||||
btn.style.textAlign = 'center';
|
|
||||||
btn.style.fontSize = '10px';
|
|
||||||
btn.style.cursor = 'hand';
|
|
||||||
btn.style.width = `${bw}px`;
|
|
||||||
btn.style.height = `${bh}px`;
|
|
||||||
btn.style.left = `${left}px`;
|
|
||||||
btn.style.top = '0px';
|
|
||||||
|
|
||||||
mxEvent.addListener(btn, 'click', function(evt) {
|
|
||||||
funct();
|
|
||||||
mxEvent.consume(evt);
|
|
||||||
});
|
|
||||||
|
|
||||||
left += bw;
|
|
||||||
|
|
||||||
buttons.appendChild(btn);
|
|
||||||
}
|
|
||||||
|
|
||||||
addButton('+', function() {
|
|
||||||
graph.zoomIn();
|
|
||||||
});
|
|
||||||
|
|
||||||
addButton('-', function() {
|
|
||||||
graph.zoomOut();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (container.nextSibling != null) {
|
|
||||||
container.parentNode.insertBefore(buttons, container.nextSibling);
|
|
||||||
} else {
|
|
||||||
container.appendChild(buttons);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(divs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -508,6 +508,7 @@ Be aware of the properties that have been renamed or whose value types have chan
|
||||||
|
|
||||||
**Migration example**
|
**Migration example**
|
||||||
|
|
||||||
|
If you want to write code to migrate mxGraph to maxGraph style, you can have a look at `packages/core/src/serialization/codecs/mxGraph/utils.ts`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Before
|
// Before
|
||||||
|
|
Loading…
Reference in New Issue