# Scaling the edge length of a graph to be equal to edge weight

I've imported a dxf file in Mathematica

dxf = Import["input.dxf"]


The following graph is present in the dxf file available here.

The actual dimensions don't match the value displayed in the text label (in yellow) over the lines. For instance, 62 is the value displayed and 54.0833 is the actual dimension. And I want to rescale the actual lengths to the values displayed in yellow-colored label over the lines.

I understand the coordinates displayed in the input provided above have to be varied. Probably, the first coordinate can be fixed and the subsequent coordinates can be shifted.

I found a similar post here and I would like to try the solution posted there (also added below).

g = Graph[vertices, edges, EdgeWeight -> weights,
EdgeLabels -> MapThread[Rule, {edges, weights}],
GraphLayout -> {"LayeredEmbedding", "Orientation" -> Top,
"RootVertex" -> 1}, EdgeLabelStyle -> Directive[Blue, 20],
VertexLabels -> "Name"];

coords = GraphEmbedding[g];
update[1] = 0;
"DiscoverVertex" -> (w = PropertyValue[{g, #2 \[DirectedEdge] #1}, EdgeWeight];
If[NumberQ[w], update[#1] = update[#2] + w]; &)];

add = update /@ VertexList[g];
{x, y} = Transpose[coords];
y = y - add/50;
ncoord1 = Transpose[{x, y}];

SetProperty[g, VertexCoordinates -> ncoord1]


To try the above solution on the imported dxf, first I have to convert the dxf into a graph object after import. I am not sure how to convert dxf to graph object in Mathematica and I'd also like to know if the solution provided in the above-mentioned post can be used for my input.

Any suggestions on how to proceed will be really helpful.

EDIT: I'm trying to clarify here

What do I want to achieve?

Example: Actual edge length between nodes 7 and 6 : computed as euclidean distance between the coordinates of 7 and 6 is 54.08.

I want to scale this length to 62.

If one directly loads the input file in AutoCAD, the yellow text displayed in the following image is the actual dimensions

And I want to convert the dimensions to the corresponding yellow labels displayed in the following image:

Addressing the following comment

It seems that the DXF file was saved in a perspective view, and that's why none of the edge labels match the lengths of the corresponding lines

The labels displayed in the second image in EDIT2 were altered externally using an AutoLISP code to merely show how the actual lengths of the corresponding lines have to be scaled. In the first image displayed in EDIT2, the edge labels exactly match the lengths of the corresponding lines.

• I'm confused. DXF stores geometrical information, not a graph. Don't you just want to rescale the whole thing proportionally? Apr 24, 2020 at 17:29
• @Szabolcs Yes you are absolutely right and thanks for correcting the terminology that I use. It's the geometry that is stored in DXF. I did try the re-scale option that you are referring to. The problem is it rescales the whole geometry and what I want to do with the geometry is to scale just the edges (if I may call the geometry as graph). Apr 25, 2020 at 2:27
• I came across the post here and was wondering if I could try a similar approach. i.e import the geometry from dxf, convert to a graph, and scale the edge lengths to edge weights (i.e. yellow labels in dxf). Apr 25, 2020 at 2:27
• I do not understand what you are saying. There is nothing but edges in this drawing. You should at least upload that file somewhere, and explain in more detail what the problem is. Apr 27, 2020 at 8:15
• @Szabolcs I have already shared the link to input file in my original post. I am sharing it again link Apr 27, 2020 at 8:35

{dxf, edges, vd} = Import["(...path...)/input.dxf", #] & /@
{"Graphics3D", "LineData", "VertexData"};

edges = UndirectedEdge @@@ edges;


gives

dxf = Graphics3D[{{EdgeForm[], {RGBColor[0., 0., 0.],
{Text[StyleForm["1", FontColor -> RGBColor[1., 0., 0.]], {75., 25., 0.}, {0, 0}],
Text[StyleForm["2", FontColor -> RGBColor[1., 0., 0.]], {115., 45., 0.}, {0, 0}],
Text[StyleForm["3", FontColor -> RGBColor[1., 0., 0.]], {90., 60., 0.}, {0, 0}],
Text[StyleForm["4", FontColor -> RGBColor[1., 0., 0.]], {10., 5., 0.}, {0, 0}],
Text[StyleForm["5", FontColor -> RGBColor[1., 0., 0.]], {45., 0., 0.}, {0, 0}],
Text[StyleForm["6", FontColor -> RGBColor[1., 0., 0.]], {45., 55., 0.}, {0, 0}],
Text[StyleForm["7", FontColor -> RGBColor[1., 0., 0.]], {0., 25., 0.}, {0, 0}],
Text[StyleForm["8", FontColor -> RGBColor[1., 0., 0.]], {10., 50., 0.}, {0, 0}],
Text[StyleForm["9", FontColor -> RGBColor[1., 0., 0.]], {115., 25., 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{75., 25., 0.}, {115., 45., 0.}}]},
Text[StyleForm["49.6", FontColor -> RGBColor[1., 1., 0.]], {95., 35., 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{75., 25., 0.}, {10., 5., 0.}}]},
Text[StyleForm["74.4", FontColor -> RGBColor[1., 1., 0.]], {42.5, 15., 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{75., 25., 0.}, {45., 0., 0.}}]},
Text[StyleForm["49.6", FontColor -> RGBColor[1., 1., 0.]], {60., 12.5, 0.}, {0, 0}],
{RGBColor[0., 0., 0.],  Line[{{115., 45., 0.}, {90., 60., 0.}}]},
Text[StyleForm["37.2", FontColor -> RGBColor[1., 1., 0.]], {102.5, 52.5, 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{115., 45., 0.}, {45., 55., 0.}}]},
Text[StyleForm["74.4", FontColor -> RGBColor[1., 1., 0.]], {80., 50., 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{90., 60., 0.}, {45., 55., 0.}}]},
Text[StyleForm["49.6", FontColor -> RGBColor[1., 1., 0.]], {67.5, 57.5, 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{10., 5., 0.}, {45., 0., 0.}}]},
Text[StyleForm["37.2", FontColor -> RGBColor[1., 1., 0.]], {27.5, 2.5, 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{10., 5., 0.}, {0., 25., 0.}}]},
Text[StyleForm["24.8", FontColor -> RGBColor[1., 1., 0.]], {5., 15., 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{45., 55., 0.}, {0., 25., 0.}}]},
Text[StyleForm["62", FontColor -> RGBColor[1., 1., 0.]], {22.5, 40., 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{0., 25., 0.}, {10., 50., 0.}}]},
Text[StyleForm["37.2", FontColor -> RGBColor[1., 1., 0.]], {5., 37.5, 0.}, {0, 0}],
{RGBColor[0., 0., 0.], Line[{{115., 45., 0.}, {115., 25., 0.}}]},
Text[StyleForm["24.8", FontColor -> RGBColor[1., 1., 0.]], {115., 35., 0.},
{0, 0}]}}}, {EdgeForm[], {RGBColor[0., 0., 0.], {}}}},
Boxed -> False, Lighting -> "Neutral"]


edges = {1 <-> 2, 1 <-> 3, 1 <-> 4, 2 <-> 5, 2 <-> 6, 5 <-> 6,
3 <-> 4, 3 <-> 7, 6 <-> 7, 7 <-> 8, 2 <-> 9};

vd = {{75., 25., 0}, {115., 45., 0}, {10., 5., 0}, {45., 0, 0},
{90., 60., 0}, {45., 55., 0}, {0, 25., 0}, {10., 50., 0}, {115.,  25.,0}};

vl = Range[Length@vd];

vcoords = MapIndexed[#2[[1]] -> # &, vd];
ew = # -> ToExpression[#2] & @@@
Partition[Cases[Replace[dxf, {_, Line[x_]} :>  UndirectedEdge @@
(Replace[Round@x, KeyMap[Round][Association[Reverse /@ vcoords]], All]),
All], {___, p : PatternSequence[_UndirectedEdge, _Text] ..} :>
Sequence @@ ({p} /. Text[t_, ___] :> t[[1]]), All], 2];

g3d = Graph3D[vl, edges, VertexCoordinates -> vcoords,
EdgeWeight -> ew, VertexLabels -> Placed["Name", Center],
EdgeLabels -> {e_ :> Placed["EdgeWeight", Center]},
VertexSize -> .3, VertexStyle -> Red]


Graph[vl, edges, VertexCoordinates -> {v_ :> vd[[v, ;; 2]]},
EdgeWeight -> ew, VertexLabels -> Placed["Name", Center],
EdgeLabels -> {e_ :> Placed["EdgeWeight", .5]}, VertexSize -> .3,
VertexStyle -> Red, ImageSize -> Large]


1. GraphLayout -> {"SpringElectricalEmbedding", "EdgeWeighted" -> True}:

Graph[vl, edges,
GraphLayout -> {"SpringElectricalEmbedding", "EdgeWeighted" -> True},
EdgeWeight -> ew, VertexLabels -> Placed["Name", Center],
EdgeLabels -> {e_ :> Placed["EdgeWeight", .5]}, VertexSize -> .3,
VertexStyle -> Red, ImageSize -> Large]


Graph3D[vl, edges,
GraphLayout -> {"SpringElectricalEmbedding", "EdgeWeighted" -> True},
EdgeWeight -> ew, VertexLabels -> Placed["Name", Center],
EdgeLabels -> {e_ :> Placed["EdgeWeight", .5]}, VertexSize -> .3,
VertexStyle -> Red, ImageSize -> Large]


2. Use NMinimize to get the vertex coordinates:

vars = Array[Through[{x, y} @ #] &, Length @ vd];

λ = 1.;
obj = Total[(Norm[vars[[First@#]] - vars[[Last@#]]] - # /. ew)^2 & /@ EdgeList[g3d]] +
λ Total[Norm /@ (vars - vd[[All, ;; 2]])];

lbnd = 0;
ubnd = 500;

solution = Last@Minimize[{obj, And @@ Thread[lbnd <= Join @@ vars <= ubnd]},
Join @@ vars];

edgeLengths = # -> Norm[Through[{x, y}@First[#]] - Through[{x, y}@Last[#]]] /.
solution & /@ EdgeList[g3d];

Grid[Prepend[{#, # /. ew, # /. edgeLengths} & /@
EdgeList[g3d], {"edge", "EdgeWeight", "Edge Length"}],
Dividers -> All]


Graph[vl, edges,
VertexCoordinates -> {v_ :> ({x[v], y[v]} /. solution)},
EdgeWeight -> ew, VertexLabels -> Placed["Name", Center],
EdgeLabels -> {e_ :> Placed["EdgeWeight", .3]}, VertexSize -> .7,
VertexStyle -> Red]


Note: You can play with different values for λ to weight the two terms in the objective function differently. You may have to play with different values for the bounds lbnd and ubnd in case NMinimize gives an error/warning message.

Update: We can use the same approach to get 3D vertex coordinates:

vars3d = Array[Through[{x, y, z}@#] &, Length @ vd];

λ = 1/100.;

obj3d = Total[(Norm[vars3d[[First@#]] - vars3d[[Last@#]]] - # /. ew)^2 & /@
EdgeList[g3d]] +  λ Total[Norm /@ (vars3d - vd)];

lbnd = 0;
ubnd = 500;

solution3d = Last@Minimize[{obj3d, And @@ Thread[lbnd <= Join @@ vars3d <= ubnd]},
Join @@ vars3d];

edgeLengths3d = # -> Norm[vars3d[[First@#]] - vars3d[[Last@#]]] /.
solution3d & /@ EdgeList[g3d];

Grid[Prepend[{#, # /. ew, # /. edgeLengths3d} & /@
EdgeList[g3d], {"edge", "EdgeWeight", "Edge Length"}],
Dividers -> All]


Graph3D[vl, edges,
VertexCoordinates -> {v_ :> ({x[v], y[v], z[v]} /. solution3d)},
EdgeWeight -> ew, VertexLabels -> Placed["Name", Center],
EdgeLabels -> {e_ :> Placed["EdgeWeight", .5]}, VertexSize -> .3,
VertexStyle -> Red, ImageSize -> Large]


• Thank you so much. Just a small request, can we avoid displaying the completepath to the github link in the answer provided? Could you please modify it to input.dxf? Apr 28, 2020 at 5:18
• The solution that you have provided is good. But my only concern is the coordinates of the vertices have shift drastically. From what I understand this is due to vertex coordinates given as outputs by the optimization task after minimizing the cost function. But can we impose additional constraints to restrict the movement of the vertex coordinates around the neighbourhood of the coordinates in the original input (i.e input.dxf)? Apr 28, 2020 at 5:40
• Brilliant! Could you please suggest how to export this back in dxf format? Apr 28, 2020 at 6:30
• Is it possible to automate this step where we specify the edges?edges = {1 <-> 2, 1 <-> 3, 1 <-> 4, 2 <-> 5, 2 <-> 6, 5 <-> 6, 3 <-> 4, 3 <-> 7, 6 <-> 7, 7 <-> 8, 2 <-> 9}; I think this is where the other test case fails Apr 29, 2020 at 3:12
• @Natasha, for specifying edges, you can use edges = UndirectedEdge @@@ Import["input.dxf", "LineData"]; Re "Unable to find a point..." you can play with the hard-coded lower and upper bounds (0 and 500) for the decision variables. And for exporting, try Export["filename.dxf", Show[Graph3D[g]]] where g is the output from Graph[vl, edges,...] or from Graph3D[vl, edges,...].
– kglr
Apr 29, 2020 at 3:25

No solution yet, but here's a way to get data from the DXF file for a start, including making a graph from the DXF edges and vertices.

I think the best approach to reproduce the labeled edge values is transform the vertex coordinates to undo the effect of perspective.

Start with Import, then click on the dxf mesh region. Use View Options to select the Top view, and Mesh Decoration to select Show edge labels and Show vertex labels.

Notice that the vertex numbers are different than your image. Use the edge numbers from the mesh region to match the edge labels from your image. For example, edge 9 is 62.

labels={49.6,74.4,49.6,37.2,74.4,49.6,28.4,24.8,62,37.2,24.8};


Use Import["input.dxf", "Graphics3D"] to a get rotatable graphics version of the DXF file that shows the labeled edges and vertices. You can get the vertex coordinates and other data from the DXF file like this:

vp = Import["input.dxf", "ViewPoint"];
lo = Import["input.dxf", "LineObjects"];
ld = Import["input.dxf", "LineData"];(*vertex numbers at line end-points*)
vd = Import["input.dxf", "VertexData"];(*vertex coordinates*)


For testing, when you have new, transformed vertex coordinates, you can make line objects from the new vertex coordinates with lines=Line[vdNew[[#]]]&/@ld, then check the new line lengths with ArcLength/@lines.

It seems that the DXF file was saved in a perspective view, and that's why none of the edge labels match the lengths of the corresponding lines.

edgeData = Transpose@{Range[Length[lo]], labels, ArcLength /@ lo};
TableForm[SortBy[edgeData, {#[[2 ;; 3]]} &],
TableHeadings -> {None, {"Edge", "Labels", "Length"}}]

Edge  Labels  Length
11    24.8    20.
8     24.8    22.3607
7     28.4    35.3553
10    37.2    26.9258
4     37.2    29.1548
3     49.6    39.0512
1     49.6    44.7214
6     49.6    45.2769
9     62      54.0833
2     74.4    68.0074
5     74.4    70.7107


Here's a basic graph, using line data, ld, and weights for each edge.

ew = Normal@AssociationThread[UndirectedEdge @@@ ld, labels];
g = Graph[Sort[UndirectedEdge @@@ ld], VertexLabels -> Automatic,
EdgeLabels -> Automatic, EdgeWeight -> ew]


Is think this gives you a some data to work with.

• @creidhe Thanks a lot for the response . Could you please check EDIT2? Apr 28, 2020 at 3:04