Rewrite part of graph layout code (#2207)

* Rewrite node placement and edge routing parts of graph layout code
* Document the high level structure of layout algorithm
* Tighter layout and less edge crossings
* Better worst case memory and CPU usage
This commit is contained in:
karliss 2020-06-03 18:36:44 +03:00 committed by GitHub
parent 1e9b82839e
commit 54ecc33ca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 4446 additions and 456 deletions

View File

@ -762,7 +762,7 @@ WARNINGS = YES
# will automatically be disabled.
# The default value is: YES.
WARN_IF_UNDOCUMENTED = YES
WARN_IF_UNDOCUMENTED = NO
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters
@ -950,7 +950,7 @@ EXAMPLE_RECURSIVE = NO
# that contain images that are to be included in the documentation (see the
# \image command).
IMAGE_PATH =
IMAGE_PATH = doxygen-images/graph_grid_layout
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
@ -1127,7 +1127,7 @@ IGNORE_PREFIX =
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = NO
GENERATE_HTML = YES
# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of

View File

@ -0,0 +1,664 @@
<?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"
width="95mm"
height="60mm"
viewBox="0 0 95 60"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="grid.svg">
<defs
id="defs2">
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2306"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(-0.6)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path2304" />
</marker>
<marker
inkscape:collect="always"
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker1726"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1724"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker1388"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1386"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)" />
</marker>
<marker
inkscape:collect="always"
inkscape:isstock="true"
style="overflow:visible"
id="Arrow2Mend"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(-0.6)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path1115" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="Arrow2Lend"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Lend">
<path
transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path1109" />
</marker>
<marker
style="overflow:visible"
refY="0"
refX="0"
orient="auto"
inkscape:stockid="Arrow2Sstart"
inkscape:isstock="true"
id="Arrow2Sstart">
<path
transform="matrix(0.3,0,0,0.3,-0.69,0)"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"
id="Arrow2SstartPath"
d="M 8.72,4.03 -2.21,0.02 8.72,-4 c -1.75,2.37 -1.74,5.62 0,8.03 z" />
</marker>
<marker
style="overflow:visible"
refY="0"
refX="0"
orient="auto"
inkscape:stockid="Arrow2Send"
inkscape:isstock="true"
id="Arrow2Send">
<path
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"
id="Arrow2SendPath"
d="M 8.72,4.03 -2.21,0.02 8.72,-4 c -1.75,2.37 -1.74,5.62 0,8.03 z" />
</marker>
<marker
id="Arrow2Sstart-1"
inkscape:isstock="true"
inkscape:stockid="Arrow2Sstart"
orient="auto"
refX="0"
refY="0"
style="overflow:visible">
<path
d="M 8.72,4.03 -2.21,0.02 8.72,-4 c -1.75,2.37 -1.74,5.62 0,8.03 z"
id="Arrow2SstartPath-8"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"
transform="matrix(0.3,0,0,0.3,-0.69,0)" />
</marker>
<marker
id="Arrow2Send-7"
inkscape:isstock="true"
inkscape:stockid="Arrow2Send"
orient="auto"
refX="0"
refY="0"
style="overflow:visible">
<path
d="M 8.72,4.03 -2.21,0.02 8.72,-4 c -1.75,2.37 -1.74,5.62 0,8.03 z"
id="Arrow2SendPath-9"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"
transform="matrix(-0.3,0,0,-0.3,0.69,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker1726-0"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(-0.6)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
id="path1724-2" />
</marker>
<marker
id="Arrow2Sstart-7"
inkscape:isstock="true"
inkscape:stockid="Arrow2Sstart"
orient="auto"
refX="0"
refY="0"
style="overflow:visible">
<path
d="M 8.72,4.03 -2.21,0.02 8.72,-4 c -1.75,2.37 -1.74,5.62 0,8.03 z"
id="Arrow2SstartPath-5"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"
transform="matrix(0.3,0,0,0.3,-0.69,0)" />
</marker>
<marker
id="Arrow2Send-9"
inkscape:isstock="true"
inkscape:stockid="Arrow2Send"
orient="auto"
refX="0"
refY="0"
style="overflow:visible">
<path
d="M 8.72,4.03 -2.21,0.02 8.72,-4 c -1.75,2.37 -1.74,5.62 0,8.03 z"
id="Arrow2SendPath-2"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"
transform="matrix(-0.3,0,0,-0.3,0.69,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0"
refX="0"
id="marker1726-0-3"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1724-2-6"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="scale(-0.6)" />
</marker>
</defs>
<sodipodi:namedview
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
inkscape:guide-bbox="true"
showguides="true"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="233.18761"
inkscape:cy="199.03882"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1389"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<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>
<g
transform="translate(-28.381867,-41.18436)"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<text
id="text888"
y="25.535202"
x="31.039022"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="25.535202"
x="31.039022"
id="tspan886"
sodipodi:role="line"></tspan></text>
<text
id="text892"
y="54.462158"
x="39.601284"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="54.462158"
x="39.601284"
id="tspan890"
sodipodi:role="line"></tspan><tspan
id="tspan894"
style="stroke-width:0.264583"
y="57.989944"
x="39.601284"
sodipodi:role="line">0</tspan></text>
<text
id="text898"
y="57.95274"
x="54.8783"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="57.95274"
x="54.8783"
id="tspan896"
sodipodi:role="line">1</tspan></text>
<text
id="text902"
y="53.987171"
x="46.333523"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="53.987171"
x="46.333523"
id="tspan900"
sodipodi:role="line">0</tspan></text>
<text
id="text906"
y="53.949966"
x="62.174358"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="53.949966"
x="62.174358"
id="tspan904"
sodipodi:role="line">1</tspan></text>
<text
id="text910"
y="57.989944"
x="69.638489"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="57.989944"
x="69.638489"
id="tspan908"
sodipodi:role="line">2</tspan></text>
<text
id="text914"
y="53.987171"
x="77.340141"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="53.987171"
x="77.340141"
id="tspan912"
sodipodi:role="line">2</tspan></text>
<text
id="text918"
y="57.989944"
x="84.606102"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="57.989944"
x="84.606102"
id="tspan916"
sodipodi:role="line">3</tspan></text>
<text
id="text922"
y="53.987171"
x="92.408722"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="53.987171"
x="92.408722"
id="tspan920"
sodipodi:role="line">3</tspan></text>
<text
id="text926"
y="62.487526"
x="34.014694"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="62.487526"
x="34.014694"
id="tspan924"
sodipodi:role="line">0</tspan></text>
<text
id="text930"
y="69.829063"
x="30.262985"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="69.829063"
x="30.262985"
id="tspan928"
sodipodi:role="line">0</tspan></text>
<text
id="text934"
y="77.488907"
x="34.08773"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="77.488907"
x="34.08773"
id="tspan932"
sodipodi:role="line">1</tspan></text>
<text
id="text938"
y="85.418694"
x="30.336021"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="85.418694"
x="30.336021"
id="tspan936"
sodipodi:role="line">1</tspan></text>
<text
id="text942"
y="92.507507"
x="34.109779"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="92.507507"
x="34.109779"
id="tspan940"
sodipodi:role="line">2</tspan></text>
<text
id="text958"
y="53.987171"
x="104.5532"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="53.987171"
x="104.5532"
id="tspan956"
sodipodi:role="line">column</tspan></text>
<text
id="text962"
y="57.442863"
x="104.42841"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="57.442863"
x="104.42841"
id="tspan960"
sodipodi:role="line">edge column</tspan></text>
<text
transform="rotate(90)"
id="text966"
y="-29.273321"
x="43.529709"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="-29.273321"
x="43.529709"
id="tspan964"
sodipodi:role="line">row</tspan></text>
<text
transform="rotate(90)"
id="text970"
y="-34.055241"
x="43.230721"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="-34.055241"
x="43.230721"
id="tspan968"
sodipodi:role="line">edge row</tspan></text>
<rect
inkscape:tile-y0="29.999999"
inkscape:tile-x0="29.999999"
style="fill:none;stroke:#000000;stroke-width:0.0930854;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
id="use986"
width="64.899994"
height="4.9000001"
x="38.048389"
y="59.010197" />
<rect
inkscape:tile-y0="29.999999"
inkscape:tile-x0="29.999999"
style="fill:none;stroke:#000000;stroke-width:0.0930854;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
id="use988"
width="64.899994"
height="4.9000001"
x="38.048389"
y="74.010193" />
<rect
inkscape:tile-y0="29.999999"
inkscape:tile-x0="29.999999"
style="fill:none;stroke:#000000;stroke-width:0.0930854;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
id="use990"
width="64.899994"
height="4.9000001"
x="38.048389"
y="93.170723" />
<rect
y="-102.94839"
x="59.010201"
height="4.9000001"
width="39.061729"
id="use986-2"
style="fill:none;stroke:#000000;stroke-width:0.068298;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
inkscape:tile-x0="29.999999"
inkscape:tile-y0="29.999999"
transform="rotate(90)" />
<rect
y="-87.948387"
x="59.010201"
height="4.9000001"
width="39.061729"
id="use988-9"
style="fill:none;stroke:#000000;stroke-width:0.068298;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
inkscape:tile-x0="29.999999"
inkscape:tile-y0="29.999999"
transform="rotate(90)" />
<rect
y="-72.948387"
x="59.010201"
height="4.9000001"
width="39.061729"
id="use990-1"
style="fill:none;stroke:#000000;stroke-width:0.068298;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
inkscape:tile-x0="29.999999"
inkscape:tile-y0="29.999999"
transform="rotate(90)" />
<rect
y="-57.948387"
x="59.010201"
height="4.9000001"
width="39.061729"
id="use992-2"
style="fill:none;stroke:#000000;stroke-width:0.068298;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
inkscape:tile-x0="29.999999"
inkscape:tile-y0="29.999999"
transform="rotate(90)" />
<rect
y="-42.948387"
x="59.010201"
height="4.9000001"
width="39.061729"
id="use994-7"
style="fill:none;stroke:#000000;stroke-width:0.068298;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
inkscape:tile-x0="29.999999"
inkscape:tile-y0="29.999999"
transform="rotate(90)" />
<rect
y="64.578438"
x="58.635674"
height="8.5201521"
width="23.725426"
id="rect1026"
style="fill:none;stroke:#000000;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal" />
<rect
style="fill:none;stroke:#000000;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
id="rect1026-0"
width="23.725426"
height="12.675932"
x="43.635674"
y="79.704514" />
<rect
y="79.54509"
x="73.635674"
height="8.5201521"
width="23.725426"
id="rect1026-0-9"
style="fill:none;stroke:#000000;stroke-width:0.3;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal" />
<path
sodipodi:nodetypes="ccccc"
d="m 69.327077,73.163441 v 3.205877 H 55.388909 v 0 3.30445"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
id="path1085" />
<path
sodipodi:nodetypes="cccc"
id="path1384"
d="m 71.333422,73.07529 v 3.336512 h 13.367971 v 3.090428"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker1388)" />
<text
id="text1428"
y="84.689873"
x="81.704636"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="84.689873"
x="81.704636"
id="tspan1426"
sodipodi:role="line">(2, 1)</tspan></text>
<text
xml:space="preserve"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
x="66.704636"
y="69.723213"
id="text1428-6"><tspan
sodipodi:role="line"
id="tspan1426-0"
x="66.704636"
y="69.723213"
style="stroke-width:0.264583">(1, 0)</tspan></text>
<text
xml:space="preserve"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
x="51.704636"
y="86.927185"
id="text1428-62"><tspan
sodipodi:role="line"
id="tspan1426-6"
x="51.704636"
y="86.927185"
style="stroke-width:0.264583">(0, 1)</tspan></text>
<path
id="path1646"
d="m 85.036709,74.435098 -0.0099,1.431015"
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow2Sstart);marker-end:url(#Arrow2Send)" />
<text
id="text1720"
y="70.049362"
x="100.38699"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
xml:space="preserve"><tspan
style="stroke-width:0.264583"
y="70.049362"
x="100.38699"
id="tspan1718"
sodipodi:role="line">segment offset</tspan></text>
<path
sodipodi:nodetypes="cc"
id="path1722"
d="M 100.24627,70.152448 85.644108,75.098641"
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264583, 0.529166;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1726)" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow2Sstart-1);marker-end:url(#Arrow2Send-7)"
d="m 68.570639,77.662689 2.143251,0.0099"
id="path1646-2" />
<path
sodipodi:nodetypes="cc"
id="path2057"
d="m 71.333422,76.411802 v 1.307449"
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264583, 0.264583;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264583, 0.529167;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1726-0)"
d="M 100.24627,70.152448 72.043698,77.555282"
id="path1722-3"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cccccc"
id="path2302"
d="m 85.503654,88.13046 0.02905,1.836997 h 13.86685 V 76.894177 H 87.138853 v 2.494365"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker2306)" />
<path
sodipodi:nodetypes="cc"
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow2Sstart-7);marker-end:url(#Arrow2Send-9)"
d="m 88.729129,90.472837 -0.0099,2.246409"
id="path1646-28" />
<text
xml:space="preserve"
style="font-size:2.82223px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
x="101.43018"
y="90.213501"
id="text1720-9"><tspan
sodipodi:role="line"
id="tspan1718-7"
x="101.43018"
y="90.213501"
style="stroke-width:0.264583">negative</tspan><tspan
id="tspan2627"
sodipodi:role="line"
x="101.43018"
y="93.741287"
style="stroke-width:0.264583">segment offset</tspan></text>
<path
sodipodi:nodetypes="cc"
id="path1722-3-1"
d="M 102.58403,91.354862 89.524132,91.884014"
style="fill:none;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264583, 0.529167;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker1726-0-3)" />
</g>
<g
transform="translate(-28.381867,-41.18436)"
style="display:inline"
inkscape:label="Layer 2"
id="layer2"
inkscape:groupmode="layer" />
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -562,6 +562,7 @@ HEADERS += \
common/BugReporting.h \
common/HighDpiPixmap.h \
widgets/GraphLayout.h \
widgets/GraphGridLayout.h \
widgets/HexWidget.h \
common/SelectionHighlight.h \
common/Decompiler.h \
@ -574,9 +575,11 @@ HEADERS += \
common/IOModesController.h \
common/SettingsUpgrade.h \
dialogs/LayoutManager.h \
common/CutterLayout.h
common/CutterLayout.h \
common/BinaryTrees.h \
common/LinkedListPool.h
GRAPHVIZ_HEADERS = widgets/GraphGridLayout.h
GRAPHVIZ_HEADERS = widgets/GraphvizLayout.h
FORMS += \
dialogs/AboutDialog.ui \

419
src/common/BinaryTrees.h Normal file
View File

@ -0,0 +1,419 @@
#ifndef BINARY_TREES_H
#define BINARY_TREES_H
/** \file BinaryTrees.h
* \brief Utilities to simplify creation of specialized augmented binary trees.
*/
#include <vector>
#include <cstdlib>
#include <climits>
#include <cstdint>
#include <algorithm>
/**
* Not really a segment tree for storing segments as referred in academic literature. Can be considered a
* full, almost perfect, augmented binary tree. In the context of competitive programming often called segment tree.
*
* Child classes are expected to implement updateFromChildren(NodeType&parent, NodeType& left, NodeType& right)
* method which calculates inner node values from children nodes.
*
* \tparam NodeTypeT type of each tree element
* \tparam FinalType final child class used for curiously recurring template pattern
*/
template<class NodeTypeT, class FinalType>
class SegmentTreeBase
{
public:
using NodePosition = size_t;
using NodeType = NodeTypeT;
/**
* @brief Create tree with \a size leaves.
* @param size number of leaves in the tree
*/
explicit SegmentTreeBase(size_t size)
: size(size)
, nodeCount(2 * size)
, nodes(nodeCount)
{}
/**
* @brief Create a tree with given size and initial value.
*
* Inner nodes are calculated from leaves.
* @param size number of leaves
* @param initialValue initial leave value
*/
SegmentTreeBase(size_t size, const NodeType &initialValue)
: SegmentTreeBase(size)
{
init(initialValue);
}
protected:
// Curiously recurring template pattern
FinalType &This()
{
return static_cast<FinalType &>(*this);
}
// Curiously recurring template pattern
const FinalType &This() const
{
return static_cast<const FinalType &>(*this);
}
size_t leavePositionToIndex(NodePosition pos) const
{
return pos - size;
}
NodePosition leaveIndexToPosition(size_t index) const
{
return index + size;
}
bool isLeave(NodePosition position) const
{
return position >= size;
}
/**
* @brief Calculate inner node values from leaves.
*/
void buildInnerNodes()
{
for (size_t i = size - 1; i > 0; i--) {
This().updateFromChildren(nodes[i], nodes[i << 1], nodes[(i << 1) | 1]);
}
}
/**
* @brief Initialize leaves with given value.
* @param value value that will be assigned to leaves
*/
void init(const NodeType &value)
{
std::fill_n(nodes.begin() + size, size, value);
buildInnerNodes();
}
const size_t size; //< number of leaves and also index of left most leave
const size_t nodeCount;
std::vector<NodeType> nodes;
};
/**
* \brief Tree for point modification and range queries.
*/
template<class NodeType, class FinalType>
class PointSetSegmentTree : public SegmentTreeBase<NodeType, FinalType>
{
using BaseType = SegmentTreeBase<NodeType, FinalType>;
public:
using BaseType::BaseType;
/**
* @brief Set leave \a index to \a value.
* @param index Leave index, should be in the range [0,size)
* @param value
*/
void set(size_t index, const NodeType &value)
{
auto pos = this->leaveIndexToPosition(index);
this->nodes[pos] = value;
while (pos > 1) {
auto parrent = pos >> 1;
this->This().updateFromChildren(this->nodes[parrent], this->nodes[pos], this->nodes[pos ^ 1]);
pos = parrent;
}
}
const NodeType &valueAtPoint(size_t index) const
{
return this->nodes[this->leaveIndexToPosition(index)];
}
// Implement range query when necessary
};
class PointSetMinTree : public PointSetSegmentTree<int, PointSetMinTree>
{
using BaseType = PointSetSegmentTree<int, PointSetMinTree>;
public:
using NodeType = int;
using BaseType::BaseType;
void updateFromChildren(NodeType &parent, NodeType &leftChild, NodeType &rightChild)
{
parent = std::min(leftChild, rightChild);
}
/**
* @brief Find right most position with value than less than given in range [0; position].
* @param position inclusive right side of query range
* @param value search for position less than this
* @return returns the position with searched property or -1 if there is no such position.
*/
int rightMostLessThan(size_t position, int value)
{
auto isGood = [&](size_t pos) {
return nodes[pos] < value;
};
// right side exclusive range [l;r)
size_t goodSubtree = 0;
for (size_t l = leaveIndexToPosition(0), r = leaveIndexToPosition(position + 1); l < r;
l >>= 1, r >>= 1) {
if (l & 1) {
if (isGood(l)) {
// mark subtree as good but don't stop yet, there might be something good further to the right
goodSubtree = l;
}
++l;
}
if (r & 1) {
--r;
if (isGood(r)) {
goodSubtree = r;
break;
}
}
}
if (!goodSubtree) {
return -1;
}
// find rightmost good leave
while (goodSubtree < size) {
goodSubtree = (goodSubtree << 1) + 1;
if (!isGood(goodSubtree)) {
goodSubtree ^= 1;
}
}
return leavePositionToIndex(goodSubtree);
}
/**
* @brief Find left most position with value less than \a value in range [position; size).
* @param position inclusive left side of query range
* @param value search for position less than this
* @return returns the position with searched property or -1 if there is no such position.
*/
int leftMostLessThan(size_t position, int value)
{
auto isGood = [&](size_t pos) {
return nodes[pos] < value;
};
// right side exclusive range [l;r)
size_t goodSubtree = 0;
for (size_t l = leaveIndexToPosition(position), r = leaveIndexToPosition(size); l < r;
l >>= 1, r >>= 1) {
if (l & 1) {
if (isGood(l)) {
goodSubtree = l;
break;
}
++l;
}
if (r & 1) {
--r;
if (isGood(r)) {
goodSubtree = r;
// mark subtree as good but don't stop yet, there might be something good further to the left
}
}
}
if (!goodSubtree) {
return -1;
}
// find leftmost good leave
while (goodSubtree < size) {
goodSubtree = (goodSubtree << 1);
if (!isGood(goodSubtree)) {
goodSubtree ^= 1;
}
}
return leavePositionToIndex(goodSubtree);
}
};
/**
* \brief Tree that supports lazily applying an operation to range.
*
* Each inner node has a promise value describing an operation that needs to be applied to corresponding subtree.
*
* Child classes are expected to implement to pushDown(size_t nodePosition) method. Which applies the applies the
* operation stored in \a promise for nodePosition to the direct children nodes.
*
* \tparam NodeType type of tree nodes
* \tparam PromiseType type describing operation that needs to be applied to subtree
* \tparam FinalType child class type for CRTP. See SegmentTreeBase
*/
template <class NodeType, class PromiseType, class FinalType>
class LazySegmentTreeBase : public SegmentTreeBase<NodeType, FinalType>
{
using BaseType = SegmentTreeBase<NodeType, FinalType>;
public:
/**
* @param size Number of tree leaves.
* @param neutralPromise Promise value that doesn't modify tree nodes.
*/
LazySegmentTreeBase(size_t size, const PromiseType &neutralPromise)
: BaseType(size)
, neutralPromiseElement(neutralPromise)
, promise(size, neutralPromise)
{
h = 0;
size_t v = size;
while (v) {
v >>= 1;
h++;
}
}
LazySegmentTreeBase(size_t size, NodeType value, PromiseType neutralPromise)
: LazySegmentTreeBase(size, neutralPromise)
{
this->init(value);
}
/**
* @brief Calculate the tree operation over the range [\a l, \a r)
* @param l inclusive range left side
* @param r exclusive range right side
* @param initialValue Initial value for aggregate operation.
* @return Tree operation calculated over the range.
*/
NodeType rangeOperation(size_t l, size_t r, NodeType initialValue)
{
NodeType result = initialValue;
l = this->leaveIndexToPosition(l);
r = this->leaveIndexToPosition(r);
pushDownFromRoot(l);
pushDownFromRoot(r - 1);
for (; l < r; l >>= 1, r >>= 1) {
if (l & 1) {
This().updateFromChildren(result, result, this->nodes[l++]);
}
if (r & 1) {
This().updateFromChildren(result, result, this->nodes[--r]);
}
}
return result;
}
protected:
/**
* @brief Ensure that all the parents of node \a p have the operation applied.
* @param p Node position
*/
void pushDownFromRoot(typename BaseType::NodePosition p)
{
for (size_t i = h; i > 0; i--) {
This().pushDown(p >> i);
}
}
/**
* @brief Update all the inner nodes in path from \a p to root.
* @param p node position
*/
void updateUntilRoot(typename BaseType::NodePosition p)
{
while (p > 1) {
auto parent = p >> 1;
if (promise[parent] == neutralPromiseElement) {
This().updateFromChildren(this->nodes[parent], this->nodes[p & ~size_t(1)], this->nodes[p | 1]);
}
p = parent;
}
}
using BaseType::This;
int h; //< Tree height
const PromiseType neutralPromiseElement;
std::vector<PromiseType> promise;
};
/**
* @brief Structure supporting range assignment and range maximum operations.
*/
class RangeAssignMaxTree : public LazySegmentTreeBase<int, uint8_t, RangeAssignMaxTree>
{
using BaseType = LazySegmentTreeBase<int, uint8_t, RangeAssignMaxTree>;
public:
using ValueType = int;
RangeAssignMaxTree(size_t size, ValueType initialValue)
: BaseType(size, initialValue, 0)
{
}
void updateFromChildren(NodeType &parent, const NodeType &left, const NodeType &right)
{
parent = std::max(left, right);
}
void pushDown(size_t parent)
{
if (promise[parent]) {
size_t left = (parent << 1);
size_t right = (parent << 1) | 1;
nodes[left] = nodes[right] = nodes[parent];
if (left < size) {
promise[left] = promise[parent];
}
if (right < size) {
promise[right] = promise[parent];
}
promise[parent] = neutralPromiseElement;
}
}
/**
* @brief Change all the elements in range [\a left, \a right) to \a value.
* @param left inclusive range left side
* @param right exclusive right side of range
* @param value value to be assigned
*/
void setRange(size_t left, size_t right, NodeType value)
{
left = leaveIndexToPosition(left);
right = leaveIndexToPosition(right);
pushDownFromRoot(left);
pushDownFromRoot(right - 1);
for (size_t l = left, r = right; l < r; l >>= 1, r >>= 1) {
if (l & 1) {
nodes[l] = value;
if (!isLeave(l)) {
promise[l] = 1;
}
l += 1;
}
if (r & 1) {
r -= 1;
nodes[r] = value;
if (!isLeave(r)) {
promise[r] = 1;
}
}
}
updateUntilRoot(left);
updateUntilRoot(right - 1);
}
/**
* @brief Calculate biggest value in the range [l, r)
* @param l inclusive left side of range
* @param r exclusive right side of range
* @return biggest value in given range
*/
int rangeMaximum(size_t l, size_t r)
{
return rangeOperation(l, r, std::numeric_limits<ValueType>::min());
}
};
#endif // BINARY_TREES_H

170
src/common/LinkedListPool.h Normal file
View File

@ -0,0 +1,170 @@
#ifndef LINKED_LIST_POOL_H
#define LINKED_LIST_POOL_H
#include <vector>
#include <cstdint>
#include <iterator>
/**
* @brief Pool of singly linked lists.
*
* Should not be used as general purpose container. Use only for algorithms that require linked lists ability
* to split and concatenate them. All the data is owned by LinkedListPool.
*
* In contrast to std::list and std::forward_list doesn't allocate each node separately. LinkedListPool can reserve
* all the memory for multiple lists during construction. Uses std::vector as backing container.
*/
template<class T>
class LinkedListPool
{
using IndexType = size_t;
struct Item {
IndexType next;
T value;
};
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.
*
* Iterators don't get invalidated by adding items to list, but the items may be relocated.
*/
class ListIterator
{
IndexType index = 0;
LinkedListPool<T> *pool = nullptr;
ListIterator(IndexType index, LinkedListPool<T> *pool)
: index(index)
, pool(pool)
{}
friend class LinkedListPool<T>;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using difference_type = size_t;
using pointer = T*;
using reference = T&;
ListIterator() = default;
reference operator*()
{
return pool->data[index].value;
}
ListIterator &operator++()
{
index = pool->data[index].next;
return *this;
}
ListIterator operator++(int)
{
ListIterator tmp(*this);
operator++();
return tmp;
}
bool operator!=(const ListIterator &b) const
{
return index != b.index || pool != b.pool;
};
/**
* @brief Test if iterator points to valid value.
*/
operator bool() const
{
return index;
}
};
/**
* @brief Create a linked list pool with capacity for \a initialCapacity list items.
* @param initialCapacity number of elements to preallocate.
*/
LinkedListPool(size_t initialCapacity)
: data(1)
{
data.reserve(initialCapacity + 1); // [0] element reserved
}
/**
* @brief Create a list containing single item.
*
* Does not invalidate any iterators, but may cause item relocation when initialCapacity is exceeded.
* @param value value of element that will be inserted in the created list
* @return List containing single value \a value .
*/
List makeList(const T &value)
{
size_t position = data.size();
data.push_back(Item{0, value});
return {position, position};
}
/**
* @brief Split list and return second half.
*
* After performing the operation, list passed as argument and return list point to the same items. Modifying them
* will affect both lists.
*
* @param list The list that needs to be split.
* @param head Iterator to the first item in new list. Needs to be within \a list .
* @return Returns suffix of \a list.
*/
List splitTail(const List &list, const ListIterator &head)
{
return List {head.index, list.tail};
}
/**
* @brief Create list iterator from list.
* @param list
* @return Iterator pointing to the first item in the list.
*/
ListIterator head(const List &list)
{
return iteratorFromIndex(list.head);
}
ListIterator end(const List &list)
{
return std::next(iteratorFromIndex(list.tail));
}
List append(const List &head, const List &tail)
{
List result{head.head, tail.tail};
data[head.tail].next = tail.head;
return result;
}
private:
ListIterator iteratorFromIndex(IndexType index)
{
return ListIterator{ index, this };
}
std::vector<Item> data;
};
#endif // LINKED_LIST_POOL

View File

@ -134,7 +134,7 @@ private:
};
/**
* @class This class is used to draw the left pane of the disassembly
* This class is used to draw the left pane of the disassembly
* widget. Its goal is to draw proper arrows for the jumps of the disassembly.
*/
class DisassemblyLeftPanel: public QFrame

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,12 @@
#include "core/Cutter.h"
#include "GraphLayout.h"
#include "common/LinkedListPool.h"
/**
* @brief Graph layout algorithm on layered graph layout approach. For simplicity all the nodes are placed in a grid.
*/
class GraphGridLayout : public GraphLayout
{
public:
@ -20,42 +25,53 @@ public:
int &height) const override;
private:
LayoutType layoutType;
/// false - use bounding box for smallest subtree when placing them side by side
bool tightSubtreePlacement = false;
/// true if code should try to place parent between direct children as much as possible
bool parentBetweenDirectChild = false;
/// false if blocks in rows should be aligned at top, true for middle alignment
bool verticalBlockAlignmentMiddle = false;
struct GridBlock {
ut64 id;
std::vector<ut64> tree_edge; // subset of outgoing edges that form a tree
std::vector<ut64> dag_edge; // subset of outgoing edges that form a tree
std::vector<ut64> tree_edge; //!< subset of outgoing edges that form a tree
std::vector<ut64> dag_edge; //!< subset of outgoing edges that form a dag
std::size_t has_parent = false;
int level = 0;
int inputCount = 0;
int outputCount = 0;
// Number of rows in block
/// Number of rows in subtree
int row_count = 0;
// Number of columns in block
int col_count = 0;
// Column in which the block is
/// Column in which the block is
int col = 0;
// Row in which the block is
/// Row in which the block is
int row = 0;
int lastRowLeft; //!< left side of subtree last row
int lastRowRight; //!< right side of subtree last row
int leftPosition; //!< left side of subtree
int rightPosition; //!< right side of subtree
LinkedListPool<int>::List leftSideShape;
LinkedListPool<int>::List rightSideShape;
};
struct Point {
int row; //point[0]
int col; //point[1]
int index; //point[2]
int row;
int col;
int offset;
int16_t kind;
int16_t spacingOverride;
};
struct GridEdge {
ut64 dest;
int mainColumn = -1;
std::vector<Point> points;
int start_index = 0;
QPolygonF polyline;
int secondaryPriority;
void addPoint(int row, int col, int index = 0)
void addPoint(int row, int col, int16_t kind = 0)
{
Point point = {row, col, 0};
this->points.push_back(point);
if (int(this->points.size()) > 1)
this->points[this->points.size() - 2].index = index;
this->points.push_back({row, col, 0, kind, 0});
}
};
@ -63,29 +79,92 @@ private:
std::unordered_map<ut64, GridBlock> grid_blocks;
std::unordered_map<ut64, GraphBlock> *blocks = nullptr;
std::unordered_map<ut64, std::vector<GridEdge>> edge;
size_t rows = -1;
size_t columns = -1;
std::vector<int> columnWidth;
std::vector<int> rowHeight;
std::vector<int> edgeColumnWidth;
std::vector<int> edgeRowHeight;
std::vector<int> columnOffset;
std::vector<int> rowOffset;
std::vector<int> edgeColumnOffset;
std::vector<int> edgeRowOffset;
};
using GridBlockMap = std::unordered_map<ut64, GridBlock>;
/**
* @brief Find nodes where control flow merges after splitting.
* Sets node column offset so that after computing placement merge point is centered bellow nodes above.
*/
void findMergePoints(LayoutState &state) const;
/**
* @brief Compute node rows and columns within grid.
* @param blockOrder Nodes in the reverse topological order.
*/
void computeAllBlockPlacement(const std::vector<ut64> &blockOrder,
LayoutState &layoutState) const;
void computeBlockPlacement(ut64 blockId,
LayoutState &layoutState) const;
void adjustGraphLayout(GridBlock &block, GridBlockMap &blocks,
int col, int row) const;
/**
* @brief Perform the topological sorting of graph nodes.
* If the graph contains loops, a subset of edges is selected. Subset of edges forming DAG are stored in
* GridBlock::dag_edge.
* @param state Graph layout state including the input graph.
* @param entry Entrypoint node. When removing loops prefer placing this node at top.
* @return Reverse topological ordering.
*/
static std::vector<ut64> topoSort(LayoutState &state, ut64 entry);
// Edge computing stuff
template<typename T>
using Matrix = std::vector<std::vector<T>>;
using EdgesVector = Matrix<std::vector<bool>>;
/**
* @brief Assign row positions to nodes.
* @param state
* @param blockOrder reverse topological ordering of nodes
*/
static void assignRows(LayoutState &state, const std::vector<ut64> &blockOrder);
/**
* @brief Select subset of DAG edges that form tree.
* @param state
*/
static void selectTree(LayoutState &state);
GridEdge routeEdge(EdgesVector &horiz_edges, EdgesVector &vert_edges,
Matrix<bool> &edge_valid, GridBlock &start, GridBlock &end) const;
static int findVertEdgeIndex(EdgesVector &edges, int col, int min_row, int max_row);
static bool isEdgeMarked(EdgesVector &edges, int row, int col, int index);
static void markEdge(EdgesVector &edges, int row, int col, int index, bool used = true);
static int findHorizEdgeIndex(EdgesVector &edges, int row, int min_col, int max_col);
/**
* @brief routeEdges Route edges, expects node positions to be calculated previously.
*/
void routeEdges(LayoutState &state) const;
/**
* @brief Choose which column to use for transition from start node row to target node row.
*/
void calculateEdgeMainColumn(LayoutState &state) const;
/**
* @brief Do rough edge routing within grid using up to 5 segments.
*/
void roughRouting(LayoutState &state) const;
/**
* @brief Calculate segment placement relative to their columns.
*/
void elaborateEdgePlacement(LayoutState &state) const;
/**
* @brief Recalculate column widths, trying to compensate for the space taken by edge columns.
*/
void adjustColumnWidths(LayoutState &state) const;
/**
* @brief Calculate position of each column(or row) based on widths.
* It is assumed that columnWidth.size() + 1 = edgeColumnWidth.size() and they are interleaved.
* @param columnWidth
* @param edgeColumnWidth
* @param columnOffset
* @param edgeColumnOffset
* @return total width of all the columns
*/
static int calculateColumnOffsets(const std::vector<int> &columnWidth, std::vector<int> &edgeColumnWidth,
std::vector<int> &columnOffset, std::vector<int> &edgeColumnOffset);
/**
* @brief Final graph layout step. Convert grids cell relative positions to absolute pixel positions.
* @param state
* @param width image width output argument
* @param height image height output argument
*/
void convertToPixelCoordinates(LayoutState &state, int &width, int &height) const;
};
#endif // GRAPHGRIDLAYOUT_H

View File

@ -32,8 +32,10 @@ public:
using Graph = std::unordered_map<ut64, GraphBlock>;
struct LayoutConfig {
int block_vertical_margin = 40;
int block_horizontal_margin = 10;
int blockVerticalSpacing = 40;
int blockHorizontalSpacing = 10;
int edgeVerticalSpacing = 10;
int edgeHorizontalSpacing = 10;
};
GraphLayout(const LayoutConfig &layout_config) : layoutConfig(layout_config) {}