Commit 47564638 authored by Raphaël Bleuse's avatar Raphaël Bleuse

Refactor SVG export

parent 148b1e44
Pipeline #7633 passed with stage
in 35 seconds
......@@ -5,11 +5,8 @@ import functools
import itertools
import logging
import svgwrite
from . import check
from . import core
from . import drawTemplate as svg
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
......@@ -180,102 +177,3 @@ class Tree:
tree.leaf = child
return tree
return tree
def getTorsions(matrix):
return [matrix[i][i] for i in range(len(matrix))]
def drawSVGTemplate(matrix, shortestPath, output, entireTemplate=False, white=False, scale=1.0):
colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22",
"#17becf", "#aec7e8", "#ffbb78", "#98df8a", "#ff9896", "#c5b0d5", "#c49c94", "#f7b6d2", "#c7c7c7",
"#dbdb8d", "#9edae5"]
dwg = svgwrite.Drawing()
lengthTorsion = 40 * scale
lengthPermut = 100 * scale
torsions = getTorsions(matrix)
maxTorsions = abs(max(torsions, key=abs))
coord = []
start_height = 100 * scale
if white:
colors = ["white"] * 20
if entireTemplate:
start_height += len(matrix) * 100 * scale
for i in range(len(matrix)):
coord.append([(i * 100 * scale + start_height, start_height) for j in range(len(shortestPath)+maxTorsions+1)])
for i in range(len(matrix)-1):
svg.upperSemiCircle(coord[i][0][0] + 40 * scale, coord[i][0][1], coord[i+1][0][0], coord[i+1][0][1], dwg, scale)
for i in range(1,maxTorsions+1):
for j in range(len(coord)):
coord[j][i] = (coord[j][i-1][0], coord[j][i-1][1]+lengthTorsion)
#make torsion
if torsions[j] == 0:
svg.straightTransition(coord[j][i-1][0], coord[j][i-1][1], dwg, lengthTorsion, colors[j%20], scale)
elif torsions[j] > 0 :
svg.positiveTorsion(coord[j][i-1][0], coord[j][i-1][1], dwg, colors[j%20], scale)
torsions[j] -= 1
else:
svg.negativeTorsion(coord[j][i-1][0], coord[j][i-1][1], dwg, colors[j%20], scale)
torsions[j] += 1
for i in range(maxTorsions+1, len(coord[0])):
round = shortestPath[i - maxTorsions - 1]
for transformation in round:
coord[transformation[0]][i], coord[transformation[1]][i] = \
(coord[transformation[1]][i-1][0],coord[transformation[1]][i-1][1]+lengthPermut), \
(coord[transformation[0]][i-1][0],coord[transformation[0]][i-1][1]+lengthPermut)
#make permut
#positive permutation
if matrix[transformation[0]][transformation[1]] > 0:
if coord[transformation[0]][i-1][0] < coord[transformation[1]][i-1][0]:
svg.leftPermut(coord[transformation[0]][i-1][0], coord[transformation[0]][i-1][1],
dwg, colors[transformation[0]%20], scale)
svg.rightPermut(coord[transformation[1]][i-1][0], coord[transformation[1]][i-1][1],
dwg, colors[transformation[1]%20], scale)
else :
svg.leftPermut(coord[transformation[1]][i-1][0], coord[transformation[1]][i-1][1],
dwg, colors[transformation[1]%20], scale)
svg.rightPermut(coord[transformation[0]][i-1][0], coord[transformation[0]][i-1][1],
dwg, colors[transformation[0]%20], scale)
#negative permutation
else :
if coord[transformation[0]][i-1][0] < coord[transformation[1]][i-1][0]:
svg.rightPermut(coord[transformation[1]][i-1][0], coord[transformation[1]][i-1][1],
dwg, colors[transformation[1]%20], scale)
svg.leftPermut(coord[transformation[0]][i-1][0], coord[transformation[0]][i-1][1],
dwg,colors[transformation[0]%20], scale)
else :
svg.rightPermut(coord[transformation[0]][i-1][0], coord[transformation[0]][i-1][1],
dwg,colors[transformation[0]%20], scale)
svg.leftPermut(coord[transformation[1]][i-1][0], coord[transformation[1]][i-1][1],
dwg,colors[transformation[1]%20], scale)
for j in range(len(coord)):
if coord[j][i] == coord[j][0]:
coord[j][i] = (coord[j][i-1][0], coord[j][i-1][1]+lengthPermut)
#draw straight line
svg.straightTransition(coord[j][i-1][0], coord[j][i-1][1], dwg, lengthPermut, colors[j%20], scale)
finalPosition = core.final_position(matrix)
left_coord = ([coord[finalPosition[0]][-1][0], coord[finalPosition[0]][-1][1] + 100 * scale])
right_coord = ([coord[finalPosition[-1]][-1][0] + 40 * scale, coord[finalPosition[-1]][-1][1] + 100 * scale])
for position in finalPosition:
svg.bottom(coord[position][-1][0], coord[position][-1][1], left_coord[0], left_coord[1], right_coord[0], right_coord[1],
dwg, colors[position%20], scale)
x,y = coord[0][0][0], coord[0][0][1]
x2,y2 = coord[-1][0][0] + 40 * scale, coord[-1][0][1]
if not entireTemplate:
top_left = (x, y)
top_right = (x2, y2)
svg.top(top_left[0], top_left[1], top_right[0], top_right[1], dwg, scale)
else:
x3,y3 = x - 80 * scale, y
x4,y4 = x3 - (x2 - x), y
x5,y5 = coord[finalPosition[0]][-1][0], coord[finalPosition[0]][-1][1] + 100 * scale
x6,y6 = coord[finalPosition[-1]][-1][0] + 40 * scale, y5
x7,y7 = x3, y5
x8,y8 = x4, y5
svg.upperSemiCircle(x3 + 1, y3, x, y, dwg, scale)
svg.upperSemiCircle(x4 + 1, y4, x2 - 1, y2, dwg, scale)
svg.lowerSemiCircle(x7 + 1, y7, x5, y5, dwg, scale)
svg.lowerSemiCircle(x8 + 1, y8, x6 - 1, y6, dwg, scale)
svg.straightLine(x3, y3, y5 - y, dwg, scale)
svg.straightLine(x4, y4, y5 - y, dwg, scale)
dwg.write(output)
This diff is collapsed.
......@@ -81,6 +81,16 @@ class Template:
return cls(matrix)
@property
def size(self):
"""Number of strands of the template."""
return len(self.matrix)
@property
def torsions(self):
"""Number of (oriented) torsions for each strand of the template."""
return [self.matrix[i][i] for i in range(len(self.matrix))]
@property
def crosslevels(self):
"""Level-by-level list of concurrent crossings of the template."""
......
import math
def goToCoord(x,y):
return "M "+str(int(x))+" "+str(int(y))
def bezier(x,y):
return "c 0 "+str(int(y/2))+" "+str(int(x))+" "+str(int(y)/2)+" "+str(int(x))+" "+str(int(y))
def goVertical(height):
return "v "+str(int(height))
def goHorizontal(breadth):
return "h "+str(int(breadth))
def finishPath():
return "z"
def leftPermut(x, y, drawing, color="white", scale=1):
p = drawing.path(style="fill:"+color)
p.push(goToCoord(x,y))
p.push(bezier(100 * scale, 100 * scale))
p.push(goVertical(1))
p.push(goHorizontal(40 * scale))
p.push(goVertical(-1))
p.push(bezier(-100 * scale, -100 * scale))
p.push(finishPath())
drawing.add(p)
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x,y))
p.push(bezier(100 * scale, 100 * scale))
p.push(goVertical(1))
p.push(goHorizontal(1))
p.push(goVertical(-1))
p.push(bezier(-100 * scale, -100 * scale))
p.push(finishPath())
drawing.add(p)
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x + 40 * scale,y))
p.push(bezier(100 * scale, 100 * scale))
p.push(goVertical(1))
p.push(goHorizontal(-1))
p.push(goVertical(-1))
p.push(bezier(-100 * scale, -100 * scale))
p.push(finishPath())
drawing.add(p)
def rightPermut(x, y, drawing, color = "white", scale=1):
p = drawing.path(style="fill:" + color)
p.push(goToCoord(x, y))
p.push(bezier(-100 * scale, 100 * scale))
p.push(goVertical(1))
p.push(goHorizontal(40 * scale))
p.push(goVertical(-1))
p.push(bezier(100 * scale, -100 * scale))
p.push(finishPath())
drawing.add(p)
p = drawing.path(style="fill:" + color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x, y))
p.push(bezier(-100 * scale, 100 * scale))
p.push(goVertical(1))
p.push(goHorizontal(1))
p.push(goVertical(-1))
p.push(bezier(100 * scale, -100 * scale))
p.push(finishPath())
drawing.add(p)
p = drawing.path(style="fill:" + color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x + 40 * scale, y))
p.push(bezier(-100 * scale, 100 * scale))
p.push(goVertical(1))
p.push(goHorizontal(-1))
p.push(goVertical(-1))
p.push(bezier(100 * scale, -100 * scale))
p.push(finishPath())
drawing.add(p)
def straightTransition(x, y, drawing, length, color="white", scale=1):
p = drawing.path(style="fill:"+color)
p.push(goToCoord(x, y))
p.push(goVertical(length + 1))
p.push(goHorizontal(40 * scale))
p.push(goVertical(-(length + 1)))
p.push(finishPath())
drawing.add(p)
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x, y))
p.push(goVertical(length + 1))
p.push(goHorizontal(1))
p.push(goVertical(-(length + 1)))
p.push(finishPath())
drawing.add(p)
p = drawing.path(style="fill:" + color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x + 40 * scale, y))
p.push(goVertical(length + 1))
p.push(goHorizontal(-1))
p.push(goVertical(-(length + 1)))
p.push(finishPath())
drawing.add(p)
def torsionShape(x, y, drawing, color="white", scale=1):
p = drawing.path(style="fill:"+color)
p.push(goToCoord(x, y))
p.push(bezier(40 * scale, 40 * scale))
p.push(goVertical(1))
p.push(goHorizontal(-(40 * scale)))
p.push(goVertical(-1))
p.push(bezier(40 * scale, -40 * scale))
p.push(finishPath())
drawing.add(p)
def positiveTorsion(x, y, drawing, color="white", scale=1):
torsionShape(x, y, drawing, color, scale=scale)
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x, y))
p.push(bezier((40 * scale) - 1, 40 * scale))
p.push(goVertical(1))
p.push(goHorizontal(1))
p.push(goVertical(-1))
p.push(bezier(-(40 * scale) + 1, -40 * scale))
p.push(finishPath())
drawing.add(p)
drawing.add(drawing.circle(center = (int(x + 20 * scale),int(y + 20 * scale)), r=str(int(5 * scale)), fill="white"))
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x + 40 * scale, y))
p.push(bezier(-(40 * scale) + 1, 40 * scale))
p.push(goVertical(1))
p.push(goHorizontal(-1))
p.push(goVertical(-1))
p.push(bezier(40 * scale - 1, -40 * scale))
p.push(finishPath())
drawing.add(p)
def negativeTorsion(x, y, drawing, color="white", scale=1):
torsionShape(x, y, drawing, color, scale=scale)
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x + 40 * scale, y))
p.push(bezier(-(40 * scale) + 1, 40 * scale))
p.push(goVertical(1))
p.push(goHorizontal(-1))
p.push(goVertical(-1))
p.push(bezier(40 * scale - 1, -40 * scale))
p.push(finishPath())
drawing.add(p)
drawing.add(drawing.circle(center = (int(x + 20 * scale),int(y + 20 * scale)), r=str(int(5 * scale)), fill="white"))
p = drawing.path(style="fill:"+color, stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x, y))
p.push(bezier(40 * scale - 1, 40 * scale))
p.push(goVertical(1))
p.push(goHorizontal(1))
p.push(goVertical(-1))
p.push(bezier(-(40 * scale) + 1, -40 * scale))
p.push(finishPath())
drawing.add(p)
def upperSemiCircle(x1, y1, x2, y2, drawing, scale):
p = drawing.path(stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x1, y2))
radius = (math.sqrt((int(x1)-int(x2))**2 + (int(y1)-int(y2))**2))/2
p.push_arc((x2,y2),180,radius,large_arc=True,angle_dir = "+",absolute=True)
p.push(goHorizontal(1))
p.push_arc((int(x1)-1,int(y1)),180,radius,large_arc=True,angle_dir="-",absolute=True)
p.push(finishPath())
drawing.add(p)
def straightLine(x, y, length, drawing, scale):
p = drawing.path(stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x, y))
p.push(goVertical(length))
p.push(goHorizontal(1))
p.push(goVertical(-length))
p.push(finishPath())
drawing.add(p)
def lowerSemiCircle(x1, y1, x2, y2, drawing, scale):
p = drawing.path(stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x1, y1))
radius = (math.sqrt((int(x1)-int(x2))**2 + (int(y1)-int(y2))**2))/2
p.push_arc((x2,y2),180,radius,large_arc=True,angle_dir = "-",absolute=True)
p.push(goHorizontal(1))
p.push_arc((int(x1)-1,int(y1)),180,radius,large_arc=True,angle_dir="+",absolute=True)
p.push(finishPath())
drawing.add(p)
def bottom(x1, y1, x2, y2, x3, y3, drawing, color="white", scale=1):
p = drawing.path(style="fill:"+color)
p.push(goToCoord(x1, y1))
relativ_x2 = x2 - x1
relativ_y2 = y2 - y1
p.push(bezier(relativ_x2, relativ_y2))
relativ_x3 = x3 - x2
p.push(goHorizontal(relativ_x3))
relativ_xlast = x1 - x3 + 40 * scale
p.push(bezier(relativ_xlast, -relativ_y2))
p.push(finishPath())
drawing.add(p)
p = drawing.path(stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x1, y1))
p.push(bezier(relativ_x2, relativ_y2))
p.push(goHorizontal(relativ_x3))
p.push(bezier(relativ_xlast, -relativ_y2))
p.push(goHorizontal(-1))
p.push(bezier(-relativ_xlast, relativ_y2 - 1))
p.push(goHorizontal(-relativ_x3+2))
p.push(bezier(-relativ_x2, -relativ_y2))
p.push(finishPath())
drawing.add(p)
def top(x1, y1, x2, y2, drawing, scale=1):
p = drawing.path(stroke="black", stroke_width=str(math.ceil(scale)))
p.push(goToCoord(x1, y1))
p.push(goVertical(-60 * scale))
relativ_x2 = int(x2)-int(x1)
p.push(goHorizontal(relativ_x2))
p.push(goVertical(60 * scale))
p.push(goHorizontal(-1))
p.push(goVertical(-(60 * scale - 1)))
p.push(goHorizontal(-relativ_x2 + 2))
p.push(goVertical(60 * scale -1))
p.push(finishPath())
drawing.add(p)
# -*- coding: utf-8 -*-
"""
Template export primitives.
"""
class Exporter:
_available_exporters = {}
def __init_subclass__(cls, *, alias=None, **kwargs):
super.__init_subclass__(**kwargs)
if alias is not None:
if alias in cls._available_exporters:
raise ValueError(f'Shadowing existing alias: \'{alias}\'')
cls._available_exporters[alias] = cls
@classmethod
def factory(cls, alias):
try:
return cls._available_exporters[alias]
except KeyError:
raise KeyError(f'Unknown Backend alias: \'{alias}\'') from None
def write(self, template, *, output, color=True, complete_flow=False, scale=1.0):
raise NotImplementedError
from ._svg import SVGExporter # import required for the factory to work
......@@ -4,7 +4,7 @@ import logging
import sys
from . import core
from . import _legacy # XXX get rid of this!
from . import export
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
......@@ -33,12 +33,25 @@ def run(infile, *, output, color=True, complete_flow=False, scale=1.0):
logger.info(f' {row}')
logger.info("Starting creation of the SVG template")
exporter = export.SVGExporter()
try:
if output == '-': # the special argument '-' means stdout
_legacy.drawSVGTemplate(template.matrix, template.crosslevels, sys.stdout, entireTemplate=complete_flow, white=not color, scale=scale)
exporter.write(
template,
output=sys.stdout,
color=color,
complete_flow=complete_flow,
scale=scale
)
else:
with open(output, mode='w') as fp:
_legacy.drawSVGTemplate(template.matrix, template.crosslevels, fp, entireTemplate=complete_flow, white=not color, scale=scale)
exporter.write(
template,
output=fp,
color=color,
complete_flow=complete_flow,
scale=scale
)
except OSError as err:
logger.critical(f'Unable to write output: {err}')
exit(code=2)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment