mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-18 10:56:11 +00:00
Graph optimize placment (#2255)
Add optional placement optimization pass which tries to push everything together and ignores the grid.
This commit is contained in:
parent
4685f4faaf
commit
8c52627312
543
docs/doxygen-images/graph_grid_layout/layout_compacting.svg
Normal file
543
docs/doxygen-images/graph_grid_layout/layout_compacting.svg
Normal file
@ -0,0 +1,543 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="layout_compacting.svg"
|
||||||
|
id="svg33"
|
||||||
|
viewBox="0 0 266.6575 152.92204"
|
||||||
|
version="1.2"
|
||||||
|
height="203.89606"
|
||||||
|
width="355.54333">
|
||||||
|
<metadata
|
||||||
|
id="metadata37">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
lock-margins="true"
|
||||||
|
fit-margin-bottom="10"
|
||||||
|
fit-margin-right="10"
|
||||||
|
fit-margin-left="10"
|
||||||
|
fit-margin-top="10"
|
||||||
|
inkscape:current-layer="svg33"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:cy="120.22566"
|
||||||
|
inkscape:cx="319.30409"
|
||||||
|
inkscape:zoom="2.4621622"
|
||||||
|
inkscape:snap-midpoints="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:snap-object-midpoints="true"
|
||||||
|
showgrid="false"
|
||||||
|
id="namedview35"
|
||||||
|
inkscape:window-height="1390"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
guidetolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
objecttolerance="10"
|
||||||
|
borderopacity="1"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff" />
|
||||||
|
<defs
|
||||||
|
id="defs11">
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2914">
|
||||||
|
<path
|
||||||
|
id="path2"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#005f87"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2850">
|
||||||
|
<path
|
||||||
|
id="path5"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#5f8700"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#5f8700"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2786">
|
||||||
|
<path
|
||||||
|
id="path8"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#e03030"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#e03030"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2786-3"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#e03030"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#e03030"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path8-6" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2850-7"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#5f8700"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#5f8700"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path5-5" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2914-1"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path2-2" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2914-1-3">
|
||||||
|
<path
|
||||||
|
id="path2-2-6"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#005f87"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2914-1-6">
|
||||||
|
<path
|
||||||
|
id="path2-2-2"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#005f87"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2914-8"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path2-7" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2786-9"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#e03030"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#e03030"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path8-2" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2850-0"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#5f8700"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#5f8700"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path5-2" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2786-3-3">
|
||||||
|
<path
|
||||||
|
id="path8-6-6"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#e03030"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#e03030"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2850-7-1">
|
||||||
|
<path
|
||||||
|
id="path5-5-2"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#5f8700"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#5f8700"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
orient="auto"
|
||||||
|
overflow="visible"
|
||||||
|
id="marker2914-1-9">
|
||||||
|
<path
|
||||||
|
id="path2-2-3"
|
||||||
|
stroke-width="1pt"
|
||||||
|
stroke="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="#005f87"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2914-1-3-1"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path2-2-6-9" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="marker2914-1-6-4"
|
||||||
|
overflow="visible"
|
||||||
|
orient="auto">
|
||||||
|
<path
|
||||||
|
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||||
|
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||||
|
fill="#005f87"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="1pt"
|
||||||
|
id="path2-2-2-7" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
x="19.86875"
|
||||||
|
y="20.397875"
|
||||||
|
width="101.07"
|
||||||
|
height="13.331"
|
||||||
|
stroke-width="0.1395"
|
||||||
|
id="rect13"
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-linecap:square;stroke-linejoin:bevel" />
|
||||||
|
<rect
|
||||||
|
id="rect15"
|
||||||
|
height="9.3690996"
|
||||||
|
width="24.597"
|
||||||
|
y="38.912872"
|
||||||
|
x="58.105747"
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel" />
|
||||||
|
<rect
|
||||||
|
id="rect17"
|
||||||
|
height="9.3690996"
|
||||||
|
width="24.597"
|
||||||
|
y="52.641872"
|
||||||
|
x="7.5697498"
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel" />
|
||||||
|
<rect
|
||||||
|
id="rect19"
|
||||||
|
height="9.3690996"
|
||||||
|
width="24.597"
|
||||||
|
y="52.641872"
|
||||||
|
x="108.64275"
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel" />
|
||||||
|
<path
|
||||||
|
d="m 70.404751,33.721873 v 5.2398"
|
||||||
|
marker-end="url(#marker2914)"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
id="path25"
|
||||||
|
style="fill:none" />
|
||||||
|
<path
|
||||||
|
d="m 68.750751,48.243873 v 1.8828 h -48.882 v 2.5231"
|
||||||
|
marker-end="url(#marker2786)"
|
||||||
|
stop-color="#000000"
|
||||||
|
stroke="#e03030"
|
||||||
|
stroke-miterlimit="2"
|
||||||
|
stroke-width="0.75"
|
||||||
|
style="font-variation-settings:normal;fill:none"
|
||||||
|
id="path27" />
|
||||||
|
<path
|
||||||
|
d="m 71.872751,48.256873 v 1.8696 h 49.067999 v 3.2324"
|
||||||
|
marker-end="url(#marker2850)"
|
||||||
|
stroke="#5f8700"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
id="path29"
|
||||||
|
style="fill:none" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="206.91023"
|
||||||
|
y="7.5697498"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect15-3" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="181.84999"
|
||||||
|
y="20.957253"
|
||||||
|
width="24.596998"
|
||||||
|
height="28.024694"
|
||||||
|
id="rect17-5" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="234.49074"
|
||||||
|
y="21.999752"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-6" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
id="path27-2"
|
||||||
|
style="font-variation-settings:normal;fill:none;marker-end:url(#marker2786-3)"
|
||||||
|
stroke-width="0.75"
|
||||||
|
stroke-miterlimit="2"
|
||||||
|
stroke="#e03030"
|
||||||
|
stop-color="#000000"
|
||||||
|
marker-end="url(#marker2786-3)"
|
||||||
|
d="m 217.55525,16.90075 v 1.8828 h -24.43006 v 2.5231" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
style="fill:none;marker-end:url(#marker2850-7)"
|
||||||
|
id="path29-9"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
stroke="#5f8700"
|
||||||
|
marker-end="url(#marker2850-7)"
|
||||||
|
d="m 220.67725,16.91375 v 1.8696 h 26.11198 v 3.2324" />
|
||||||
|
<rect
|
||||||
|
id="rect19-6-0"
|
||||||
|
height="9.3690987"
|
||||||
|
width="24.596998"
|
||||||
|
y="54.221745"
|
||||||
|
x="181.84999"
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel" />
|
||||||
|
<path
|
||||||
|
style="fill:none;marker-end:url(#marker2914-1)"
|
||||||
|
id="path25-7"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
stroke="#005f87"
|
||||||
|
marker-end="url(#marker2914-1)"
|
||||||
|
d="m 194.14849,48.981943 v 5.2398" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="234.49074"
|
||||||
|
y="54.221745"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-6-0-9" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
d="M 246.78923,31.368849 V 54.148584"
|
||||||
|
marker-end="url(#marker2914-1-3)"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
id="path25-7-0"
|
||||||
|
style="fill:none;marker-end:url(#marker2914-1-3)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="234.49074"
|
||||||
|
y="68.830635"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-6-0-6" />
|
||||||
|
<path
|
||||||
|
d="m 246.78923,63.590837 v 5.2398"
|
||||||
|
marker-end="url(#marker2914-1-6)"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
id="path25-7-1"
|
||||||
|
style="fill:none;marker-end:url(#marker2914-1-6)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
id="rect13-3"
|
||||||
|
stroke-width="0.1395"
|
||||||
|
height="13.330999"
|
||||||
|
width="101.06999"
|
||||||
|
y="86.583054"
|
||||||
|
x="21.267822" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="59.50481"
|
||||||
|
y="105.09805"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect15-7" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="45.492069"
|
||||||
|
y="120.35294"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect17-59" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="73.206848"
|
||||||
|
y="120.35294"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-2" />
|
||||||
|
<path
|
||||||
|
style="fill:none;marker-end:url(#marker2914-8)"
|
||||||
|
id="path25-2"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
stroke="#005f87"
|
||||||
|
marker-end="url(#marker2914-8)"
|
||||||
|
d="m 71.803816,99.907053 v 5.239787" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
id="path27-8"
|
||||||
|
style="font-variation-settings:normal;fill:none;marker-end:url(#marker2786-9)"
|
||||||
|
stroke-width="0.75"
|
||||||
|
stroke-miterlimit="2"
|
||||||
|
stroke="#e03030"
|
||||||
|
stop-color="#000000"
|
||||||
|
marker-end="url(#marker2786-9)"
|
||||||
|
d="m 70.149816,114.42904 v 1.8828 H 57.91528 v 3.82797" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
style="fill:none;marker-end:url(#marker2850-0)"
|
||||||
|
id="path29-97"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
stroke="#5f8700"
|
||||||
|
marker-end="url(#marker2850-0)"
|
||||||
|
d="m 73.271816,114.44204 v 1.8696 h 12.721232 v 3.23241" />
|
||||||
|
<g
|
||||||
|
transform="translate(-35.90579,-13.250133)"
|
||||||
|
id="g1598">
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="241.84329"
|
||||||
|
y="102.58134"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect15-3-8" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="216.78305"
|
||||||
|
y="115.96884"
|
||||||
|
width="24.596998"
|
||||||
|
height="28.024693"
|
||||||
|
id="rect17-5-4" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="269.4238"
|
||||||
|
y="117.01134"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-6-5" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
id="path27-2-0"
|
||||||
|
style="font-variation-settings:normal;fill:none;marker-end:url(#marker2786-3-3)"
|
||||||
|
stroke-width="0.75"
|
||||||
|
stroke-miterlimit="2"
|
||||||
|
stroke="#e03030"
|
||||||
|
stop-color="#000000"
|
||||||
|
marker-end="url(#marker2786-3-3)"
|
||||||
|
d="m 252.48832,111.91234 v 1.8828 h -24.43006 v 2.5231" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
style="fill:none;marker-end:url(#marker2850-7-1)"
|
||||||
|
id="path29-9-3"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
stroke="#5f8700"
|
||||||
|
marker-end="url(#marker2850-7-1)"
|
||||||
|
d="m 255.61032,111.92534 v 1.8696 h 26.11198 v 3.2324" />
|
||||||
|
<rect
|
||||||
|
id="rect19-6-0-61"
|
||||||
|
height="9.3690987"
|
||||||
|
width="24.596998"
|
||||||
|
y="149.23332"
|
||||||
|
x="216.78305"
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel" />
|
||||||
|
<path
|
||||||
|
style="fill:none;marker-end:url(#marker2914-1-9)"
|
||||||
|
id="path25-7-06"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
stroke="#005f87"
|
||||||
|
marker-end="url(#marker2914-1-9)"
|
||||||
|
d="m 229.08156,143.99353 v 5.2398" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="269.4238"
|
||||||
|
y="130.94421"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-6-0-9-3" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
d="m 281.7223,126.38044 v 4.19969"
|
||||||
|
marker-end="url(#marker2914-1-3-1)"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
id="path25-7-0-2"
|
||||||
|
style="fill:none;marker-end:url(#marker2914-1-3-1)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#f5faff;fill-rule:evenodd;stroke:#91c8fa;stroke-width:0.1395;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
x="269.4238"
|
||||||
|
y="145.55312"
|
||||||
|
width="24.596998"
|
||||||
|
height="9.3690987"
|
||||||
|
id="rect19-6-0-6-0" />
|
||||||
|
<path
|
||||||
|
d="m 281.7223,140.31331 v 5.2398"
|
||||||
|
marker-end="url(#marker2914-1-6-4)"
|
||||||
|
stroke="#005f87"
|
||||||
|
stroke-width="0.75px"
|
||||||
|
id="path25-7-1-6"
|
||||||
|
style="fill:none;marker-end:url(#marker2914-1-6-4)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 16 KiB |
@ -752,7 +752,7 @@ void Configuration::setGraphSpacing(QPoint blockSpacing, QPoint edgeSpacing)
|
|||||||
|
|
||||||
QPoint Configuration::getGraphBlockSpacing()
|
QPoint Configuration::getGraphBlockSpacing()
|
||||||
{
|
{
|
||||||
return s.value("graph.blockSpacing", QPoint(10, 40)).value<QPoint>();
|
return s.value("graph.blockSpacing", QPoint(20, 40)).value<QPoint>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QPoint Configuration::getGraphEdgeSpacing()
|
QPoint Configuration::getGraphEdgeSpacing()
|
||||||
|
@ -23,30 +23,6 @@ class LinkedListPool
|
|||||||
T value;
|
T value;
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* @brief Single list within LinkedListPool.
|
|
||||||
*
|
|
||||||
* List only refers to chain of elements. Copying it doesn't copy any element. Item data is owned by
|
|
||||||
* LinkedListPool.
|
|
||||||
*
|
|
||||||
* Use LinkedListPool::makeList to create non-empty list.
|
|
||||||
*/
|
|
||||||
class List
|
|
||||||
{
|
|
||||||
IndexType head = 0;
|
|
||||||
IndexType tail = 0;
|
|
||||||
friend class LinkedListPool;
|
|
||||||
List(IndexType head, IndexType tail)
|
|
||||||
: head(head)
|
|
||||||
, tail(tail)
|
|
||||||
{}
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Create an empty list
|
|
||||||
*/
|
|
||||||
List() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief List iterator.
|
* @brief List iterator.
|
||||||
*
|
*
|
||||||
@ -73,6 +49,11 @@ public:
|
|||||||
{
|
{
|
||||||
return pool->data[index].value;
|
return pool->data[index].value;
|
||||||
}
|
}
|
||||||
|
pointer operator->()
|
||||||
|
{
|
||||||
|
return &pool->data[index].value;
|
||||||
|
}
|
||||||
|
|
||||||
ListIterator &operator++()
|
ListIterator &operator++()
|
||||||
{
|
{
|
||||||
index = pool->data[index].next;
|
index = pool->data[index].next;
|
||||||
@ -91,12 +72,41 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Test if iterator points to valid value.
|
* @brief Test if iterator points to valid value.
|
||||||
*/
|
*/
|
||||||
operator bool() const
|
explicit operator bool() const
|
||||||
{
|
{
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Single list within LinkedListPool.
|
||||||
|
*
|
||||||
|
* List only refers to chain of elements. Copying it doesn't copy any element. Item data is owned by
|
||||||
|
* LinkedListPool.
|
||||||
|
*
|
||||||
|
* Use LinkedListPool::makeList to create non-empty list.
|
||||||
|
*/
|
||||||
|
class List
|
||||||
|
{
|
||||||
|
IndexType head = 0;
|
||||||
|
IndexType tail = 0;
|
||||||
|
friend class LinkedListPool;
|
||||||
|
List(IndexType head, IndexType tail)
|
||||||
|
: head(head)
|
||||||
|
, tail(tail)
|
||||||
|
{}
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create an empty list
|
||||||
|
*/
|
||||||
|
List() = default;
|
||||||
|
|
||||||
|
bool isEmpty() const
|
||||||
|
{
|
||||||
|
return head == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a linked list pool with capacity for \a initialCapacity list items.
|
* @brief Create a linked list pool with capacity for \a initialCapacity list items.
|
||||||
* @param initialCapacity number of elements to preallocate.
|
* @param initialCapacity number of elements to preallocate.
|
||||||
@ -136,6 +146,29 @@ public:
|
|||||||
return List {head.index, list.tail};
|
return List {head.index, list.tail};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Split list and return first half.
|
||||||
|
*
|
||||||
|
* @param list The list that needs to be split.
|
||||||
|
* @param end Iterator to the first item that should not be included in returned list. Needs to be within \a list .
|
||||||
|
* @return Returns prefix of \a list.
|
||||||
|
*/
|
||||||
|
List splitHead(const List &list, const ListIterator &end)
|
||||||
|
{
|
||||||
|
if (!end) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
if (end.index == list.head) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto last = list.head;
|
||||||
|
while (data[last].next != end.index) {
|
||||||
|
last = data[last].next;
|
||||||
|
}
|
||||||
|
data[last].next = 0;
|
||||||
|
return List {list.head, last};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create list iterator from list.
|
* @brief Create list iterator from list.
|
||||||
* @param list
|
* @param list
|
||||||
@ -153,6 +186,12 @@ public:
|
|||||||
|
|
||||||
List append(const List &head, const List &tail)
|
List append(const List &head, const List &tail)
|
||||||
{
|
{
|
||||||
|
if (head.isEmpty()) {
|
||||||
|
return tail;
|
||||||
|
}
|
||||||
|
if (tail.isEmpty()) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
List result{head.head, tail.tail};
|
List result{head.head, tail.tail};
|
||||||
data[head.tail].next = tail.head;
|
data[head.tail].next = tail.head;
|
||||||
return result;
|
return result;
|
||||||
|
@ -41,6 +41,12 @@ void GraphOptionsWidget::updateOptionsFromVars()
|
|||||||
ui->maxColsSpinBox->blockSignals(true);
|
ui->maxColsSpinBox->blockSignals(true);
|
||||||
ui->maxColsSpinBox->setValue(Config()->getGraphBlockMaxChars());
|
ui->maxColsSpinBox->setValue(Config()->getGraphBlockMaxChars());
|
||||||
ui->maxColsSpinBox->blockSignals(false);
|
ui->maxColsSpinBox->blockSignals(false);
|
||||||
|
auto blockSpacing = Config()->getGraphBlockSpacing();
|
||||||
|
ui->horizontalBlockSpacing->setValue(blockSpacing.x());
|
||||||
|
ui->verticalBlockSpacing->setValue(blockSpacing.y());
|
||||||
|
auto edgeSpacing = Config()->getGraphEdgeSpacing();
|
||||||
|
ui->horizontalEdgeSpacing->setValue(edgeSpacing.x());
|
||||||
|
ui->verticalEdgeSpacing->setValue(edgeSpacing.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,6 +38,12 @@ const int DisassemblerGraphView::KEY_ZOOM_IN = Qt::Key_Plus + Qt::ControlModifie
|
|||||||
const int DisassemblerGraphView::KEY_ZOOM_OUT = Qt::Key_Minus + Qt::ControlModifier;
|
const int DisassemblerGraphView::KEY_ZOOM_OUT = Qt::Key_Minus + Qt::ControlModifier;
|
||||||
const int DisassemblerGraphView::KEY_ZOOM_RESET = Qt::Key_Equal + Qt::ControlModifier;
|
const int DisassemblerGraphView::KEY_ZOOM_RESET = Qt::Key_Equal + Qt::ControlModifier;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define GRAPH_GRID_DEBUG_MODES true
|
||||||
|
#else
|
||||||
|
#define GRAPH_GRID_DEBUG_MODES false
|
||||||
|
#endif
|
||||||
|
|
||||||
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable,
|
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable,
|
||||||
MainWindow *mainWindow, QList<QAction *> additionalMenuActions)
|
MainWindow *mainWindow, QList<QAction *> additionalMenuActions)
|
||||||
: GraphView(parent),
|
: GraphView(parent),
|
||||||
@ -103,6 +109,18 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se
|
|||||||
{tr("Grid narrow"), GraphView::Layout::GridNarrow}
|
{tr("Grid narrow"), GraphView::Layout::GridNarrow}
|
||||||
, {tr("Grid medium"), GraphView::Layout::GridMedium}
|
, {tr("Grid medium"), GraphView::Layout::GridMedium}
|
||||||
, {tr("Grid wide"), GraphView::Layout::GridWide}
|
, {tr("Grid wide"), GraphView::Layout::GridWide}
|
||||||
|
|
||||||
|
#if GRAPH_GRID_DEBUG_MODES
|
||||||
|
, {"GridAAA", GraphView::Layout::GridAAA}
|
||||||
|
, {"GridAAB", GraphView::Layout::GridAAB}
|
||||||
|
, {"GridABA", GraphView::Layout::GridABA}
|
||||||
|
, {"GridABB", GraphView::Layout::GridABB}
|
||||||
|
, {"GridBAA", GraphView::Layout::GridBAA}
|
||||||
|
, {"GridBAB", GraphView::Layout::GridBAB}
|
||||||
|
, {"GridBBA", GraphView::Layout::GridBBA}
|
||||||
|
, {"GridBBB", GraphView::Layout::GridBBB}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
, {tr("Graphviz polyline"), GraphView::Layout::GraphvizPolyline}
|
, {tr("Graphviz polyline"), GraphView::Layout::GraphvizPolyline}
|
||||||
, {tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho}
|
, {tr("Graphviz ortho"), GraphView::Layout::GraphvizOrtho}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
#include "common/BinaryTrees.h"
|
#include "common/BinaryTrees.h"
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ one from all valid toposort orders. Example: for graph 1->4, 2->1, 2->3, 3->4 va
|
|||||||
4. assign node positions within grid using tree structure, child subtrees are placed side by side with parent on top
|
4. assign node positions within grid using tree structure, child subtrees are placed side by side with parent on top
|
||||||
5. perform edge routing
|
5. perform edge routing
|
||||||
6. calculate column and row pixel positions based on node sizes and amount edges between the rows
|
6. calculate column and row pixel positions based on node sizes and amount edges between the rows
|
||||||
|
7. [optional] layout compacting
|
||||||
|
|
||||||
|
|
||||||
Contrary to many other layered graph drawing algorithm this implementation doesn't perform node reordering to minimize
|
Contrary to many other layered graph drawing algorithm this implementation doesn't perform node reordering to minimize
|
||||||
@ -121,25 +123,56 @@ Assignment order is chosen based on:
|
|||||||
* edge length - establishes some kind of order when single node is connected to many edges, typically a block
|
* edge length - establishes some kind of order when single node is connected to many edges, typically a block
|
||||||
with switch statement or block after switch statement.
|
with switch statement or block after switch statement.
|
||||||
|
|
||||||
|
# Layout compacting
|
||||||
|
|
||||||
|
Doing the layout within a grid causes minimal spacing to be limited by widest and tallest block within each column
|
||||||
|
and row. One common case is block with function entrypoint being wider due to function name causing wide horizontal
|
||||||
|
space between branching blocks. Another case is rows in two parallel columns being aligned.
|
||||||
|
|
||||||
|
\image html layout_compacting.svg
|
||||||
|
|
||||||
|
Both problems are mitigated by squishing graph. Compressing in each of the two direction is done separately.
|
||||||
|
The process is defined as liner program. Each variable represents a position of edge segment or node in the
|
||||||
|
direction being optimized.
|
||||||
|
|
||||||
|
Following constraints are used
|
||||||
|
- Keep the order with nearest segments.
|
||||||
|
- If the node has two outgoing edges, one to the node on left side and other to the right, keep them on the corresponding side of node's center.
|
||||||
|
- For all edges keep the node which is above above. This helps when vertical block spacing is set bigger than double edge spacing and
|
||||||
|
edge shadows relationship between two blocks.
|
||||||
|
- Equality constraint to keep relative position between nodes and and segments directly connected to them.
|
||||||
|
- Equality constraint to keep the node centered when control flow merges
|
||||||
|
In the vertical direction objective function minimizes y positions of nodes and lengths of vertical segments.
|
||||||
|
In the horizontal direction objective function minimizes lengths of horizontal segments.
|
||||||
|
|
||||||
|
In the resulting linear program all constraints beside x_i >= 0 consist of exactly two variables: either x_i - x_j <= c_k or
|
||||||
|
x_i = x_j + c_k.
|
||||||
|
|
||||||
|
Since it isn't necessary get perfect solution and to avoid worst case performance current implementation isn't
|
||||||
|
using a general purpose linear programming solver. Each variable is changed until constraint is reached and afterwards
|
||||||
|
variables are grouped and changed together.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
GraphGridLayout::GraphGridLayout(GraphGridLayout::LayoutType layoutType)
|
GraphGridLayout::GraphGridLayout(GraphGridLayout::LayoutType layoutType)
|
||||||
: GraphLayout({})
|
: GraphLayout({})
|
||||||
, layoutType(layoutType)
|
|
||||||
{
|
{
|
||||||
switch (layoutType) {
|
switch (layoutType) {
|
||||||
case LayoutType::Narrow:
|
case LayoutType::Narrow:
|
||||||
tightSubtreePlacement = true;
|
tightSubtreePlacement = true;
|
||||||
parentBetweenDirectChild = false;
|
parentBetweenDirectChild = false;
|
||||||
|
useLayoutOptimization = true;
|
||||||
break;
|
break;
|
||||||
case LayoutType::Medium:
|
case LayoutType::Medium:
|
||||||
tightSubtreePlacement = false;
|
tightSubtreePlacement = false;
|
||||||
parentBetweenDirectChild = false;
|
parentBetweenDirectChild = true;
|
||||||
|
useLayoutOptimization = true;
|
||||||
break;
|
break;
|
||||||
case LayoutType::Wide:
|
case LayoutType::Wide:
|
||||||
tightSubtreePlacement = false;
|
tightSubtreePlacement = false;
|
||||||
parentBetweenDirectChild = true;
|
parentBetweenDirectChild = true;
|
||||||
|
useLayoutOptimization = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,8 +258,7 @@ void GraphGridLayout::selectTree(GraphGridLayout::LayoutState &state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphGridLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks, ut64 entry,
|
void GraphGridLayout::CalculateLayout(GraphLayout::Graph &blocks, ut64 entry, int &width, int &height) const
|
||||||
int &width, int &height) const
|
|
||||||
{
|
{
|
||||||
LayoutState layoutState;
|
LayoutState layoutState;
|
||||||
layoutState.blocks = &blocks;
|
layoutState.blocks = &blocks;
|
||||||
@ -244,6 +276,7 @@ void GraphGridLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &bloc
|
|||||||
layoutState.edge[blockIt.first].resize(blockIt.second.edges.size());
|
layoutState.edge[blockIt.first].resize(blockIt.second.edges.size());
|
||||||
for (size_t i = 0; i < blockIt.second.edges.size(); i++) {
|
for (size_t i = 0; i < blockIt.second.edges.size(); i++) {
|
||||||
layoutState.edge[blockIt.first][i].dest = blockIt.second.edges[i].target;
|
layoutState.edge[blockIt.first][i].dest = blockIt.second.edges[i].target;
|
||||||
|
blockIt.second.edges[i].arrow = GraphEdge::Down;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &edgeList : layoutState.edge) {
|
for (const auto &edgeList : layoutState.edge) {
|
||||||
@ -279,6 +312,10 @@ void GraphGridLayout::CalculateLayout(std::unordered_map<ut64, GraphBlock> &bloc
|
|||||||
routeEdges(layoutState);
|
routeEdges(layoutState);
|
||||||
|
|
||||||
convertToPixelCoordinates(layoutState, width, height);
|
convertToPixelCoordinates(layoutState, width, height);
|
||||||
|
if (useLayoutOptimization) {
|
||||||
|
optimizeLayout(layoutState);
|
||||||
|
cropToContent(blocks, width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphGridLayout::findMergePoints(GraphGridLayout::LayoutState &state) const
|
void GraphGridLayout::findMergePoints(GraphGridLayout::LayoutState &state) const
|
||||||
@ -318,6 +355,7 @@ void GraphGridLayout::findMergePoints(GraphGridLayout::LayoutState &state) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blocksGoingToMerge) {
|
if (blocksGoingToMerge) {
|
||||||
|
block.mergeBlock = mergeBlock->id;
|
||||||
state.grid_blocks[block.tree_edge[blockWithTreeEdge]].col = blockWithTreeEdge * 2 -
|
state.grid_blocks[block.tree_edge[blockWithTreeEdge]].col = blockWithTreeEdge * 2 -
|
||||||
(blocksGoingToMerge - 1);
|
(blocksGoingToMerge - 1);
|
||||||
}
|
}
|
||||||
@ -847,7 +885,7 @@ static void centerEdges(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert segment coordinates from arbitary range to continuous range starting at 0.
|
* @brief Convert segment coordinates from arbitrary range to continuous range starting at 0.
|
||||||
* @param segments
|
* @param segments
|
||||||
* @param leftSides
|
* @param leftSides
|
||||||
* @param rightSides
|
* @param rightSides
|
||||||
@ -939,7 +977,7 @@ void GraphGridLayout::elaborateEdgePlacement(GraphGridLayout::LayoutState &state
|
|||||||
edgeOffsets.resize(edgeIndex);
|
edgeOffsets.resize(edgeIndex);
|
||||||
calculateSegmentOffsets(segments, edgeOffsets, state.edgeColumnWidth, rightSides, leftSides,
|
calculateSegmentOffsets(segments, edgeOffsets, state.edgeColumnWidth, rightSides, leftSides,
|
||||||
state.columnWidth, 2 * state.rows + 1, layoutConfig.edgeHorizontalSpacing);
|
state.columnWidth, 2 * state.rows + 1, layoutConfig.edgeHorizontalSpacing);
|
||||||
centerEdges(edgeOffsets, state.edgeColumnWidth, segments, layoutConfig.blockHorizontalSpacing);
|
centerEdges(edgeOffsets, state.edgeColumnWidth, segments, layoutConfig.edgeHorizontalSpacing);
|
||||||
edgeIndex = 0;
|
edgeIndex = 0;
|
||||||
|
|
||||||
auto copySegmentsToEdges = [&](bool col) {
|
auto copySegmentsToEdges = [&](bool col) {
|
||||||
@ -970,7 +1008,7 @@ void GraphGridLayout::elaborateEdgePlacement(GraphGridLayout::LayoutState &state
|
|||||||
|
|
||||||
|
|
||||||
// Horizontal segments
|
// Horizontal segments
|
||||||
// Use exact x coordinates obtained from vertical segment placment.
|
// Use exact x coordinates obtained from vertical segment placement.
|
||||||
segments.clear();
|
segments.clear();
|
||||||
leftSides.clear();
|
leftSides.clear();
|
||||||
rightSides.clear();
|
rightSides.clear();
|
||||||
@ -1074,7 +1112,6 @@ void GraphGridLayout::convertToPixelCoordinates(
|
|||||||
auto &block = it.second;
|
auto &block = it.second;
|
||||||
for (size_t i = 0; i < block.edges.size(); i++) {
|
for (size_t i = 0; i < block.edges.size(); i++) {
|
||||||
auto &resultEdge = block.edges[i];
|
auto &resultEdge = block.edges[i];
|
||||||
const auto &target = (*state.blocks)[resultEdge.target];
|
|
||||||
resultEdge.polyline.clear();
|
resultEdge.polyline.clear();
|
||||||
resultEdge.polyline.push_back(QPointF(0, block.y + block.height));
|
resultEdge.polyline.push_back(QPointF(0, block.y + block.height));
|
||||||
|
|
||||||
@ -1092,8 +1129,559 @@ void GraphGridLayout::convertToPixelCoordinates(
|
|||||||
resultEdge.polyline.push_back(QPointF(0, y));
|
resultEdge.polyline.push_back(QPointF(0, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultEdge.polyline.back().setY(target.y);
|
}
|
||||||
|
}
|
||||||
|
connectEdgeEnds(*state.blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphGridLayout::cropToContent(GraphLayout::Graph &graph, int &width, int &height) const
|
||||||
|
{
|
||||||
|
if (graph.empty()) {
|
||||||
|
width = std::max(1, layoutConfig.edgeHorizontalSpacing);
|
||||||
|
height = std::max(1, layoutConfig.edgeVerticalSpacing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &anyBlock = graph.begin()->second;
|
||||||
|
int minPos[2] = {anyBlock.x, anyBlock.y};
|
||||||
|
int maxPos[2] = {anyBlock.x, anyBlock.y};
|
||||||
|
|
||||||
|
auto updateLimits = [&](int x, int y) {
|
||||||
|
minPos[0] = std::min(minPos[0] , x);
|
||||||
|
minPos[1] = std::min(minPos[1] , y);
|
||||||
|
maxPos[0] = std::max(maxPos[0] , x);
|
||||||
|
maxPos[1] = std::max(maxPos[1] , y);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &blockIt : graph) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
updateLimits(block.x, block.y);
|
||||||
|
updateLimits(block.x + block.width, block.y + block.height);
|
||||||
|
for (auto &edge : block.edges) {
|
||||||
|
for (auto &point: edge.polyline) {
|
||||||
|
updateLimits(point.x(), point.y());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
minPos[0] -= layoutConfig.edgeHorizontalSpacing;
|
||||||
|
minPos[1] -= layoutConfig.edgeVerticalSpacing;
|
||||||
|
maxPos[0] += layoutConfig.edgeHorizontalSpacing;
|
||||||
|
maxPos[1] += layoutConfig.edgeVerticalSpacing;
|
||||||
|
for (auto &blockIt : graph) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
block.x -= minPos[0];
|
||||||
|
block.y -= minPos[1];
|
||||||
|
for (auto &edge : block.edges) {
|
||||||
|
for (auto &point: edge.polyline) {
|
||||||
|
point -= QPointF(minPos[0], minPos[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
width = maxPos[0] - minPos[0];
|
||||||
|
height = maxPos[1] - minPos[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphGridLayout::connectEdgeEnds(GraphLayout::Graph &graph) const
|
||||||
|
{
|
||||||
|
for (auto &it : graph) {
|
||||||
|
auto &block = it.second;
|
||||||
|
for (size_t i = 0; i < block.edges.size(); i++) {
|
||||||
|
auto &resultEdge = block.edges[i];
|
||||||
|
const auto &target = graph[resultEdge.target];
|
||||||
|
resultEdge.polyline[0].ry() = block.y + block.height;
|
||||||
|
resultEdge.polyline.back().ry() = target.y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Either equality or inequality x_i <= x_j + c
|
||||||
|
using Constraint = std::pair<std::pair<int, int>, int>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Single pass of linear program optimizer.
|
||||||
|
* Changes variables until a constraint is hit, afterwards the two variables are changed together.
|
||||||
|
* @param n number of variables
|
||||||
|
* @param objectiveFunction coefficients for function \f$\sum c_i x_i\f$ which needs to be minimized
|
||||||
|
* @param inequalities inequality constraints \f$x_{e_i} - x_{f_i} \leq b_i\f$
|
||||||
|
* @param equalities equality constraints \f$x_{e_i} - x_{f_i} = b_i\f$
|
||||||
|
* @param solution input/output argument, returns results, needs to be initialized with a viable solution
|
||||||
|
* @param stickWhenNotMoving variable grouping strategy
|
||||||
|
*/
|
||||||
|
static void optimizeLinearProgramPass(
|
||||||
|
size_t n,
|
||||||
|
std::vector<int> objectiveFunction,
|
||||||
|
std::vector<Constraint> inequalities,
|
||||||
|
std::vector<Constraint> equalities,
|
||||||
|
std::vector<int> &solution,
|
||||||
|
bool stickWhenNotMoving)
|
||||||
|
{
|
||||||
|
std::vector<int> group(n);
|
||||||
|
std::iota(group.begin(), group.end(), 0); // initially each variable is in it's own group
|
||||||
|
assert(n == objectiveFunction.size());
|
||||||
|
assert(n == solution.size());
|
||||||
|
std::vector<size_t> edgeCount(n);
|
||||||
|
|
||||||
|
LinkedListPool<size_t> edgePool(inequalities.size() * 2);
|
||||||
|
|
||||||
|
std::vector<decltype(edgePool)::List> edges(n);
|
||||||
|
|
||||||
|
auto getGroup = [&](int v) {
|
||||||
|
while (group[v] != v) {
|
||||||
|
group[v] = group[group[v]];
|
||||||
|
v = group[v];
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
auto joinGroup = [&](int a, int b) {
|
||||||
|
group[getGroup(b)] = getGroup(a);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &constraint : inequalities) {
|
||||||
|
int a = constraint.first.first;
|
||||||
|
int b = constraint.first.second;
|
||||||
|
size_t index = &constraint - &inequalities.front();
|
||||||
|
edges[a] = edgePool.append(edges[a], edgePool.makeList(index));
|
||||||
|
edges[b] = edgePool.append(edges[b], edgePool.makeList(index));
|
||||||
|
edgeCount[a]++;
|
||||||
|
edgeCount[b]++;
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> processed(n);
|
||||||
|
// Smallest variable value in the group relative to main one, this is used to maintain implicit x_i >= 0
|
||||||
|
// constraint
|
||||||
|
std::vector<int> groupRelativeMin(n, 0);
|
||||||
|
|
||||||
|
auto joinSegmentGroups = [&](int a, int b) {
|
||||||
|
a = getGroup(a);
|
||||||
|
b = getGroup(b);
|
||||||
|
joinGroup(a, b);
|
||||||
|
edgeCount[a] += edgeCount[b];
|
||||||
|
objectiveFunction[a] += objectiveFunction[b];
|
||||||
|
int internalEdgeCount = 0;
|
||||||
|
auto writeIt = edgePool.head(edges[b]);
|
||||||
|
// update inequalities and remove some of the constraints between variables that are now grouped
|
||||||
|
for (auto it = edgePool.head(edges[b]); it; ++it) {
|
||||||
|
auto &constraint = inequalities[*it];
|
||||||
|
int other = constraint.first.first + constraint.first.second - b;
|
||||||
|
if (getGroup(other) == a) { // skip inequalities where both variables are now in the same group
|
||||||
|
internalEdgeCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*writeIt++ = *it;
|
||||||
|
// Modify the inequalities for the group being attached relative to the main variable in the group
|
||||||
|
// to which it is being attached.
|
||||||
|
int diff = solution[a] - solution[b];
|
||||||
|
if (b == constraint.first.first) {
|
||||||
|
constraint.first.first = a;
|
||||||
|
constraint.second += diff;
|
||||||
|
} else {
|
||||||
|
constraint.first.second = a;
|
||||||
|
constraint.second -= diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edges[a] = edgePool.append(edges[a], edgePool.splitHead(edges[b], writeIt));
|
||||||
|
edgeCount[a] -= internalEdgeCount;
|
||||||
|
groupRelativeMin[a] = std::min(groupRelativeMin[a], groupRelativeMin[b] + solution[b]- solution[a]);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &equality : equalities) {
|
||||||
|
// process equalities, assumes that initial solution is viable solution and matches equality constraints
|
||||||
|
int a = getGroup(equality.first.first);
|
||||||
|
int b = getGroup(equality.first.second);
|
||||||
|
if (a == b) {
|
||||||
|
equality = {{0, 0}, 0};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// always join smallest group to bigger one
|
||||||
|
if (edgeCount[a] > edgeCount[b]) {
|
||||||
|
std::swap(a, b);
|
||||||
|
// Update the equality equation so that later variable values can be calculated by simply iterating through
|
||||||
|
// them without need to check which direction the group joining was done.
|
||||||
|
std::swap(equality.first.first, equality.first.second);
|
||||||
|
equality.second = -equality.second;
|
||||||
|
}
|
||||||
|
joinSegmentGroups(b, a);
|
||||||
|
equality = {{a, b}, solution[a] - solution[b]};
|
||||||
|
processed[a] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority queue for processing groups starting with currently smallest one. Doing it this way should result in
|
||||||
|
// number of constraints within group doubling each time two groups are joined. That way each constraint is
|
||||||
|
// processed no more than log(n) times.
|
||||||
|
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> queue;
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
if (!processed[i]) {
|
||||||
|
queue.push({edgeCount[i], i});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!queue.empty()) {
|
||||||
|
int g = queue.top().second;
|
||||||
|
int size = queue.top().first;
|
||||||
|
queue.pop();
|
||||||
|
if (size != edgeCount[g] || processed[g]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int direction = objectiveFunction[g];
|
||||||
|
if (direction == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Find the first constraint which will be hit by changing the variable in the desired direction defined
|
||||||
|
// by objective function.
|
||||||
|
int limitingGroup = -1;
|
||||||
|
int smallestMove = 0;
|
||||||
|
if (direction < 0) {
|
||||||
|
smallestMove = INT_MAX;
|
||||||
|
for (auto it = edgePool.head(edges[g]); it; ++it) {
|
||||||
|
auto &inequality = inequalities[*it];
|
||||||
|
if (g == inequality.first.second) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int other = inequality.first.second;
|
||||||
|
if (getGroup(other) == g) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int move = solution[other] + inequality.second - solution[g];
|
||||||
|
if (move < smallestMove) {
|
||||||
|
smallestMove = move;
|
||||||
|
limitingGroup = other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
smallestMove = -solution[g] - groupRelativeMin[g]; // keep all variables >= 0
|
||||||
|
for (auto it = edgePool.head(edges[g]); it; ++it) {
|
||||||
|
auto &inequality = inequalities[*it];
|
||||||
|
if (g == inequality.first.first) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int other = inequality.first.first;
|
||||||
|
if (getGroup(other) == g) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int move = solution[other] - inequality.second - solution[g];
|
||||||
|
if (move > smallestMove) {
|
||||||
|
smallestMove = move;
|
||||||
|
limitingGroup = other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(smallestMove != INT_MAX);
|
||||||
|
solution[g] += smallestMove;
|
||||||
|
if (smallestMove == 0 && stickWhenNotMoving == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
processed[g] = 1;
|
||||||
|
if (limitingGroup != -1) {
|
||||||
|
joinSegmentGroups(limitingGroup, g);
|
||||||
|
if (!processed[limitingGroup]) {
|
||||||
|
queue.push({edgeCount[limitingGroup], limitingGroup});
|
||||||
|
}
|
||||||
|
equalities.push_back({{g, limitingGroup}, solution[g] - solution[limitingGroup]});
|
||||||
|
} // else do nothing if limited by variable >= 0
|
||||||
|
}
|
||||||
|
for (auto it = equalities.rbegin(), end = equalities.rend(); it != end; ++it) {
|
||||||
|
solution[it->first.first] = solution[it->first.second] + it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Linear programming solver
|
||||||
|
* Does not guarantee optimal solution.
|
||||||
|
* @param n number of variables
|
||||||
|
* @param objectiveFunction coefficients for function \f$\sum c_i x_i\f$ which needs to be minimized
|
||||||
|
* @param inequalities inequality constraints \f$x_{e_i} - x_{f_i} \leq b_i\f$
|
||||||
|
* @param equalities equality constraints \f$x_{e_i} - x_{f_i} = b_i\f$
|
||||||
|
* @param solution input/output argument, returns results, needs to be initialized with a viable solution
|
||||||
|
*/
|
||||||
|
static void optimizeLinearProgram(
|
||||||
|
size_t n,
|
||||||
|
const std::vector<int> &objectiveFunction,
|
||||||
|
std::vector<Constraint> inequalities,
|
||||||
|
const std::vector<Constraint> &equalities,
|
||||||
|
std::vector<int> &solution)
|
||||||
|
{
|
||||||
|
// Remove redundant inequalities
|
||||||
|
std::sort(inequalities.begin(), inequalities.end());
|
||||||
|
auto uniqueEnd = std::unique(inequalities.begin(), inequalities.end(),
|
||||||
|
[](const Constraint &a, const Constraint &b){
|
||||||
|
return a.first == b.first;
|
||||||
|
});
|
||||||
|
inequalities.erase(uniqueEnd, inequalities.end());
|
||||||
|
|
||||||
|
|
||||||
|
static const int ITERATIONS = 1;
|
||||||
|
for (int i = 0; i < ITERATIONS; i++) {
|
||||||
|
optimizeLinearProgramPass(n, objectiveFunction, inequalities, equalities, solution, true);
|
||||||
|
//optimizeLinearProgramPass(n, objectiveFunction, inequalities, equalities, solution, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct Segment {
|
||||||
|
int x;
|
||||||
|
int variableId;
|
||||||
|
int y0, y1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Constraint createInequality(size_t a, int posA, size_t b, int posB, int minSpacing, const std::vector<int> &positions)
|
||||||
|
{
|
||||||
|
minSpacing = std::min(minSpacing, posB - posA);
|
||||||
|
return {{a, b}, posB - positions[b] - (posA - positions[a]) - minSpacing};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create inequality constraints from segments which preserves their relative order on single axis.
|
||||||
|
*
|
||||||
|
* @param segments list of edge segments and block sides
|
||||||
|
* @param positions initial element positions before optimization
|
||||||
|
* @param blockCount number of variables representing blocks, it is assumed that segments with
|
||||||
|
* variableId < \a blockCount represent one side of block.
|
||||||
|
* @param variableGroup used to check if segments are part of the same edge and spacing can be reduced
|
||||||
|
* @param blockSpacing minimal spacing between blocks
|
||||||
|
* @param segmentSpacing minimal spacing between two edge segments, spacing may be less if values in \a positions
|
||||||
|
* are closer than this
|
||||||
|
* @param inequalities output variable for resulting inequalities, values initially stored in it are not removed
|
||||||
|
*/
|
||||||
|
static void createInequalitiesFromSegments(
|
||||||
|
std::vector<Segment> segments,
|
||||||
|
const std::vector<int>& positions,
|
||||||
|
const std::vector<size_t>& variableGroup,
|
||||||
|
int blockCount,
|
||||||
|
int blockSpacing,
|
||||||
|
int segmentSpacing,
|
||||||
|
std::vector<Constraint> &inequalities)
|
||||||
|
{
|
||||||
|
// map used as binary search tree y_position -> segment{variableId, x_position}
|
||||||
|
// It is used to maintain which segment was last seen in the range y_position..
|
||||||
|
std::map<int, std::pair<int, int>> lastSegments;
|
||||||
|
lastSegments[-1] = {-1, -1};
|
||||||
|
|
||||||
|
std::sort(segments.begin(), segments.end(), [](const Segment &a, const Segment &b) {
|
||||||
|
return a.x < b.x;
|
||||||
|
});
|
||||||
|
for (auto &segment : segments) {
|
||||||
|
auto startPos = lastSegments.lower_bound(segment.y0);
|
||||||
|
--startPos; // should never be lastSegment.begin() because map is initialized with segment at pos -1
|
||||||
|
auto lastSegment = startPos->second;
|
||||||
|
auto it = startPos;
|
||||||
|
while (it != lastSegments.end() && it->first <= segment.y1) {
|
||||||
|
int prevSegmentVariable = it->second.first;
|
||||||
|
int prevSegmentPos = it->second.second;
|
||||||
|
if (prevSegmentVariable != -1) {
|
||||||
|
int minSpacing = segmentSpacing;
|
||||||
|
if (prevSegmentVariable < blockCount && segment.variableId < blockCount) {
|
||||||
|
// no need to add inequality between two sides of block
|
||||||
|
if (prevSegmentVariable == segment.variableId) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
minSpacing = blockSpacing;
|
||||||
|
} else if (variableGroup[prevSegmentVariable] == variableGroup[segment.variableId]) {
|
||||||
|
minSpacing = 0;
|
||||||
|
}
|
||||||
|
inequalities.push_back(createInequality(prevSegmentVariable, prevSegmentPos,
|
||||||
|
segment.variableId, segment.x,
|
||||||
|
minSpacing, positions));
|
||||||
|
}
|
||||||
|
lastSegment = it->second;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
if (startPos->first < segment.y0) {
|
||||||
|
startPos++;
|
||||||
|
}
|
||||||
|
lastSegments.erase(startPos, it); // erase segments covered by current one
|
||||||
|
lastSegments[segment.y0] = {segment.variableId, segment.x}; // current segment
|
||||||
|
// either current segment splitting previous one into two parts or remaining part of partially covered segment
|
||||||
|
lastSegments[segment.y1] = lastSegment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphGridLayout::optimizeLayout(GraphGridLayout::LayoutState &state) const
|
||||||
|
{
|
||||||
|
std::unordered_map<uint64_t, int> blockMapping;
|
||||||
|
size_t blockIndex = 0;
|
||||||
|
for (auto &blockIt : *state.blocks) {
|
||||||
|
blockMapping[blockIt.first] = blockIndex++;
|
||||||
|
}
|
||||||
|
std::vector<size_t> variableGroups(blockMapping.size());
|
||||||
|
std::iota(variableGroups.begin(), variableGroups.end(), 0);
|
||||||
|
|
||||||
|
std::vector<int> objectiveFunction;
|
||||||
|
std::vector<Constraint> inequalities;
|
||||||
|
std::vector<Constraint> equalities;
|
||||||
|
std::vector<int> solution;
|
||||||
|
|
||||||
|
auto addObjective = [&](size_t a, int posA, size_t b, int posB) {
|
||||||
|
objectiveFunction.resize(std::max(objectiveFunction.size(), std::max(a, b) + 1));
|
||||||
|
if (posA < posB) {
|
||||||
|
objectiveFunction[b] += 1;
|
||||||
|
objectiveFunction[a] -= 1;
|
||||||
|
} else {
|
||||||
|
objectiveFunction[a] += 1;
|
||||||
|
objectiveFunction[b] -= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto addInequality = [&](size_t a, int posA, size_t b, int posB, int minSpacing) {
|
||||||
|
inequalities.push_back(createInequality(a, posA, b, posB, minSpacing, solution));
|
||||||
|
};
|
||||||
|
auto addBlockSegmentEquality = [&](ut64 blockId, int edgeVariable, int edgeVariablePos) {
|
||||||
|
int blockPos = (*state.blocks)[blockId].x;
|
||||||
|
int blockVariable = blockMapping[blockId];
|
||||||
|
equalities.push_back({{blockVariable, edgeVariable}, blockPos - edgeVariablePos});
|
||||||
|
};
|
||||||
|
auto setViableSolution = [&](size_t variable, int value) {
|
||||||
|
solution.resize(std::max(solution.size(), variable + 1));
|
||||||
|
solution[variable] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto copyVariablesToPositions = [&](const std::vector<int> &solution, bool horizontal = false) {
|
||||||
|
size_t variableIndex = blockMapping.size();
|
||||||
|
for (auto &blockIt : *state.blocks) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
for (auto &edge : blockIt.second.edges) {
|
||||||
|
for (int i = 1 + int(horizontal); i < edge.polyline.size(); i += 2) {
|
||||||
|
int x = solution[variableIndex++];
|
||||||
|
if (horizontal) {
|
||||||
|
edge.polyline[i].ry() = x;
|
||||||
|
edge.polyline[i - 1].ry() = x;
|
||||||
|
} else {
|
||||||
|
edge.polyline[i].rx() = x;
|
||||||
|
edge.polyline[i - 1].rx() = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int blockVariable = blockMapping[blockIt.first];
|
||||||
|
(horizontal ? block.y : block.x) = solution[blockVariable];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Segment> segments;
|
||||||
|
segments.reserve(state.blocks->size() * 2 + state.blocks->size() * 2);
|
||||||
|
size_t variableIndex = state.blocks->size();
|
||||||
|
size_t edgeIndex = 0;
|
||||||
|
// horizontal segments
|
||||||
|
|
||||||
|
|
||||||
|
objectiveFunction.assign(blockMapping.size(), 1);
|
||||||
|
for (auto &blockIt : *state.blocks) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
int blockVariable = blockMapping[blockIt.first];
|
||||||
|
for (auto &edge : block.edges) {
|
||||||
|
auto &targetBlock = (*state.blocks)[edge.target];
|
||||||
|
if (block.y < targetBlock.y) {
|
||||||
|
int spacing = block.height + layoutConfig.blockVerticalSpacing;
|
||||||
|
inequalities.push_back({{blockVariable, blockMapping[edge.target]}, -spacing});
|
||||||
|
}
|
||||||
|
if (edge.polyline.size() < 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int i = 2; i < edge.polyline.size(); i += 2) {
|
||||||
|
int y0 = edge.polyline[i - 1].x();
|
||||||
|
int y1 = edge.polyline[i].x();
|
||||||
|
if (y0 > y1) {
|
||||||
|
std::swap(y0, y1);
|
||||||
|
}
|
||||||
|
int x = edge.polyline[i].y();
|
||||||
|
segments.push_back({x, int(variableIndex), y0, y1});
|
||||||
|
variableGroups.push_back(blockMapping.size() + edgeIndex);
|
||||||
|
setViableSolution(variableIndex, x);
|
||||||
|
if (i > 2) {
|
||||||
|
int prevX = edge.polyline[i - 2].y();
|
||||||
|
addObjective(variableIndex, x, variableIndex - 1, prevX);
|
||||||
|
}
|
||||||
|
variableIndex++;
|
||||||
|
}
|
||||||
|
edgeIndex++;
|
||||||
|
}
|
||||||
|
segments.push_back({block.y, blockVariable, block.x, block.x + block.width});
|
||||||
|
segments.push_back({block.y + block.height, blockVariable, block.x, block.x + block.width});
|
||||||
|
setViableSolution(blockVariable, block.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
createInequalitiesFromSegments(std::move(segments), solution, variableGroups, blockMapping.size(),
|
||||||
|
layoutConfig.blockVerticalSpacing, layoutConfig.edgeVerticalSpacing, inequalities);
|
||||||
|
|
||||||
|
objectiveFunction.resize(solution.size());
|
||||||
|
optimizeLinearProgram(solution.size(), objectiveFunction, inequalities, equalities, solution);
|
||||||
|
for (auto v : solution) {
|
||||||
|
assert(v >= 0);
|
||||||
|
}
|
||||||
|
copyVariablesToPositions(solution, true);
|
||||||
|
connectEdgeEnds(*state.blocks);
|
||||||
|
|
||||||
|
// vertical segments
|
||||||
|
variableGroups.resize(blockMapping.size());
|
||||||
|
solution.clear();
|
||||||
|
equalities.clear();
|
||||||
|
inequalities.clear();
|
||||||
|
objectiveFunction.clear();
|
||||||
|
segments.clear();
|
||||||
|
variableIndex = blockMapping.size();
|
||||||
|
edgeIndex = 0;
|
||||||
|
for (auto &blockIt : *state.blocks) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
for (auto &edge : block.edges) {
|
||||||
|
if (edge.polyline.size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t firstEdgeVariable = variableIndex;
|
||||||
|
for (int i = 1; i < edge.polyline.size(); i += 2) {
|
||||||
|
int y0 = edge.polyline[i - 1].y();
|
||||||
|
int y1 = edge.polyline[i].y();
|
||||||
|
if (y0 > y1) {
|
||||||
|
std::swap(y0, y1);
|
||||||
|
}
|
||||||
|
int x = edge.polyline[i].x();
|
||||||
|
segments.push_back({x, int(variableIndex), y0, y1});
|
||||||
|
variableGroups.push_back(blockMapping.size() + edgeIndex);
|
||||||
|
setViableSolution(variableIndex, x);
|
||||||
|
if (i > 2) {
|
||||||
|
int prevX = edge.polyline[i - 2].x();
|
||||||
|
addObjective(variableIndex, x, variableIndex - 1, prevX);
|
||||||
|
}
|
||||||
|
variableIndex++;
|
||||||
|
}
|
||||||
|
size_t lastEdgeVariableIndex = variableIndex - 1;
|
||||||
|
addBlockSegmentEquality(blockIt.first, firstEdgeVariable, edge.polyline[1].x());
|
||||||
|
addBlockSegmentEquality(edge.target, lastEdgeVariableIndex, segments.back().x);
|
||||||
|
edgeIndex++;
|
||||||
|
}
|
||||||
|
int blockVariable = blockMapping[blockIt.first];
|
||||||
|
segments.push_back({block.x, blockVariable, block.y, block.y + block.height});
|
||||||
|
segments.push_back({block.x + block.width, blockVariable, block.y, block.y + block.height});
|
||||||
|
setViableSolution(blockVariable, block.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
createInequalitiesFromSegments(std::move(segments), solution, variableGroups, blockMapping.size(),
|
||||||
|
layoutConfig.blockHorizontalSpacing, layoutConfig.edgeHorizontalSpacing, inequalities);
|
||||||
|
|
||||||
|
objectiveFunction.resize(solution.size());
|
||||||
|
// horizontal centering constraints
|
||||||
|
for (auto &blockIt : *state.blocks) {
|
||||||
|
auto &block = blockIt.second;
|
||||||
|
int blockVariable = blockMapping[blockIt.first];
|
||||||
|
if (block.edges.size() == 2) {
|
||||||
|
auto &blockLeft = (*state.blocks)[block.edges[0].target];
|
||||||
|
auto &blockRight = (*state.blocks)[block.edges[1].target];
|
||||||
|
auto middle = block.x + block.width / 2;
|
||||||
|
if (blockLeft.x + blockLeft.width < middle && blockRight.x > middle) {
|
||||||
|
addInequality(blockMapping[block.edges[0].target], blockLeft.x + blockLeft.width,
|
||||||
|
blockVariable, middle,
|
||||||
|
layoutConfig.blockHorizontalSpacing / 2);
|
||||||
|
addInequality(blockVariable, middle,
|
||||||
|
blockMapping[block.edges[1].target], blockRight.x,
|
||||||
|
layoutConfig.blockHorizontalSpacing / 2);
|
||||||
|
auto &gridBlock = state.grid_blocks[blockIt.first];
|
||||||
|
if (gridBlock.mergeBlock) {
|
||||||
|
auto &mergeBlock = (*state.blocks)[gridBlock.mergeBlock];
|
||||||
|
if (mergeBlock.x + mergeBlock.width / 2 == middle) {
|
||||||
|
equalities.push_back({{blockVariable, blockMapping[gridBlock.mergeBlock]},
|
||||||
|
block.x - mergeBlock.x});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optimizeLinearProgram(solution.size(), objectiveFunction, inequalities, equalities, solution);
|
||||||
|
copyVariablesToPositions(solution);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -19,18 +19,22 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
GraphGridLayout(LayoutType layoutType = LayoutType::Medium);
|
GraphGridLayout(LayoutType layoutType = LayoutType::Medium);
|
||||||
virtual void CalculateLayout(std::unordered_map<ut64, GraphBlock> &blocks,
|
virtual void CalculateLayout(Graph &blocks,
|
||||||
ut64 entry,
|
ut64 entry,
|
||||||
int &width,
|
int &width,
|
||||||
int &height) const override;
|
int &height) const override;
|
||||||
|
void setTightSubtreePlacement(bool enabled) { tightSubtreePlacement = enabled; }
|
||||||
|
void setParentBetweenDirectChild(bool enabled) { parentBetweenDirectChild = enabled; }
|
||||||
|
void setverticalBlockAlignmentMiddle(bool enabled) { verticalBlockAlignmentMiddle = enabled; }
|
||||||
|
void setLayoutOptimization(bool enabled) { useLayoutOptimization = enabled; }
|
||||||
private:
|
private:
|
||||||
LayoutType layoutType;
|
|
||||||
/// false - use bounding box for smallest subtree when placing them side by side
|
/// false - use bounding box for smallest subtree when placing them side by side
|
||||||
bool tightSubtreePlacement = false;
|
bool tightSubtreePlacement = false;
|
||||||
/// true if code should try to place parent between direct children as much as possible
|
/// true if code should try to place parent between direct children as much as possible
|
||||||
bool parentBetweenDirectChild = false;
|
bool parentBetweenDirectChild = false;
|
||||||
/// false if blocks in rows should be aligned at top, true for middle alignment
|
/// false if blocks in rows should be aligned at top, true for middle alignment
|
||||||
bool verticalBlockAlignmentMiddle = false;
|
bool verticalBlockAlignmentMiddle = false;
|
||||||
|
bool useLayoutOptimization = true;
|
||||||
|
|
||||||
struct GridBlock {
|
struct GridBlock {
|
||||||
ut64 id;
|
ut64 id;
|
||||||
@ -47,6 +51,8 @@ private:
|
|||||||
/// Row in which the block is
|
/// Row in which the block is
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
|
ut64 mergeBlock = 0;
|
||||||
|
|
||||||
int lastRowLeft; //!< left side of subtree last row
|
int lastRowLeft; //!< left side of subtree last row
|
||||||
int lastRowRight; //!< right side of subtree last row
|
int lastRowRight; //!< right side of subtree last row
|
||||||
int leftPosition; //!< left side of subtree
|
int leftPosition; //!< left side of subtree
|
||||||
@ -165,6 +171,23 @@ private:
|
|||||||
* @param height image height output argument
|
* @param height image height output argument
|
||||||
*/
|
*/
|
||||||
void convertToPixelCoordinates(LayoutState &state, int &width, int &height) const;
|
void convertToPixelCoordinates(LayoutState &state, int &width, int &height) const;
|
||||||
|
/**
|
||||||
|
* @brief Move the graph content to top left corner and update dimensions.
|
||||||
|
* @param graph
|
||||||
|
* @param width width after cropping
|
||||||
|
* @param height height after cropping
|
||||||
|
*/
|
||||||
|
void cropToContent(Graph &graph, int &width, int &height) const;
|
||||||
|
/**
|
||||||
|
* @brief Connect edge ends to blocks by changing y.
|
||||||
|
* @param graph
|
||||||
|
*/
|
||||||
|
void connectEdgeEnds(Graph &graph) const;
|
||||||
|
/**
|
||||||
|
* @brief Reduce spacing between nodes and edges by pushing everything together ignoring the grid.
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
void optimizeLayout(LayoutState &state) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GRAPHGRIDLAYOUT_H
|
#endif // GRAPHGRIDLAYOUT_H
|
||||||
|
@ -33,7 +33,7 @@ public:
|
|||||||
|
|
||||||
struct LayoutConfig {
|
struct LayoutConfig {
|
||||||
int blockVerticalSpacing = 40;
|
int blockVerticalSpacing = 40;
|
||||||
int blockHorizontalSpacing = 10;
|
int blockHorizontalSpacing = 20;
|
||||||
int edgeVerticalSpacing = 10;
|
int edgeVerticalSpacing = 10;
|
||||||
int edgeHorizontalSpacing = 10;
|
int edgeHorizontalSpacing = 10;
|
||||||
};
|
};
|
||||||
|
@ -539,6 +539,23 @@ std::unique_ptr<GraphLayout> GraphView::makeGraphLayout(GraphView::Layout layout
|
|||||||
case Layout::GridWide:
|
case Layout::GridWide:
|
||||||
result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide));
|
result.reset(new GraphGridLayout(GraphGridLayout::LayoutType::Wide));
|
||||||
break;
|
break;
|
||||||
|
case Layout::GridAAA:
|
||||||
|
case Layout::GridAAB:
|
||||||
|
case Layout::GridABA:
|
||||||
|
case Layout::GridABB:
|
||||||
|
case Layout::GridBAA:
|
||||||
|
case Layout::GridBAB:
|
||||||
|
case Layout::GridBBA:
|
||||||
|
case Layout::GridBBB:
|
||||||
|
{
|
||||||
|
int options = static_cast<int>(layout) - static_cast<int>(Layout::GridAAA);
|
||||||
|
std::unique_ptr<GraphGridLayout> gridLayout(new GraphGridLayout());
|
||||||
|
gridLayout->setTightSubtreePlacement((options & 1) == 0);
|
||||||
|
gridLayout->setParentBetweenDirectChild((options & 2));
|
||||||
|
gridLayout->setLayoutOptimization((options & 4));
|
||||||
|
result = std::move(gridLayout);
|
||||||
|
break;
|
||||||
|
}
|
||||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
case Layout::GraphvizOrtho:
|
case Layout::GraphvizOrtho:
|
||||||
result.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho,
|
result.reset(new GraphvizLayout(GraphvizLayout::LineType::Ortho,
|
||||||
|
@ -42,6 +42,14 @@ public:
|
|||||||
GridNarrow
|
GridNarrow
|
||||||
, GridMedium
|
, GridMedium
|
||||||
, GridWide
|
, GridWide
|
||||||
|
, GridAAA
|
||||||
|
, GridAAB
|
||||||
|
, GridABA
|
||||||
|
, GridABB
|
||||||
|
, GridBAA
|
||||||
|
, GridBAB
|
||||||
|
, GridBBA
|
||||||
|
, GridBBB
|
||||||
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
#ifdef CUTTER_ENABLE_GRAPHVIZ
|
||||||
, GraphvizOrtho
|
, GraphvizOrtho
|
||||||
, GraphvizPolyline
|
, GraphvizPolyline
|
||||||
|
Loading…
Reference in New Issue
Block a user