638 lines
26 KiB
Python
638 lines
26 KiB
Python
#!/usr/bin/env python
|
|
# coding=utf-8
|
|
#
|
|
# Copyright (c) 2023 David Burghoff <burghoff@utexas.edu>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
|
|
import dhelpers as dh
|
|
import inkex
|
|
from inkex import Transform
|
|
import math, copy
|
|
from dhelpers import bbox
|
|
from inkex.text.utils import uniquetol
|
|
|
|
It = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
|
|
|
|
# Define global tags
|
|
PATHLIKE_TAGS = [
|
|
inkex.PathElement.ctag,
|
|
inkex.Rectangle.ctag,
|
|
inkex.Line.ctag,
|
|
inkex.Polyline.ctag,
|
|
]
|
|
RECTANGLE_TAG = inkex.Rectangle.ctag
|
|
TEXTLIKE_TAGS = [inkex.TextElement.ctag, inkex.FlowRoot.ctag]
|
|
GROUP_TAG = inkex.Group.ctag
|
|
EXCLUDE_TAGS = [
|
|
inkex.Tspan.ctag,
|
|
inkex.NamedView.ctag,
|
|
inkex.Defs.ctag,
|
|
inkex.Metadata.ctag,
|
|
inkex.ForeignObject.ctag,
|
|
]
|
|
|
|
|
|
def geometric_bbox(el, vis_bbox, irange=None):
|
|
gbb = copy.copy(vis_bbox)
|
|
if el.tag in PATHLIKE_TAGS: # if path-like, use nodes instead
|
|
xs, ys = dh.get_points(el, irange=irange)
|
|
# For clipped objects the list of points is a bad description of the
|
|
# geometric bounding box. As a rough workaround, use the visual bbox if
|
|
# its limits are smaller than the geometric bbox. I think this is almost
|
|
# always fine since clips use the geometric bbox.
|
|
minx = max(min(xs), vis_bbox[0])
|
|
maxx = min(max(xs), vis_bbox[0] + vis_bbox[2])
|
|
miny = max(min(ys), vis_bbox[1])
|
|
maxy = min(max(ys), vis_bbox[1] + vis_bbox[3])
|
|
gbb = [minx, miny, maxx - minx, maxy - miny] # geometric bounding box
|
|
return bbox(gbb)
|
|
|
|
|
|
# Determines plot area from a list of elements and their geometric bounding boxes
|
|
def Find_Plot_Area(els, gbbs):
|
|
vl = dict() # vertical lines
|
|
hl = dict() # horizontal lines
|
|
boxes = dict()
|
|
solids = dict()
|
|
plotareas = dict()
|
|
for el in list(reversed(els)):
|
|
isrect = False
|
|
if el.tag in PATHLIKE_TAGS:
|
|
gbb = gbbs[el.get_id()]
|
|
xs, ys = dh.get_points(el)
|
|
if (max(xs) - min(xs)) < 0.001 * gbb[3]:
|
|
vl[el.get_id()] = gbb
|
|
if (max(ys) - min(ys)) < 0.001 * gbb[2]:
|
|
hl[el.get_id()] = gbb
|
|
|
|
tol = 1e-3 * max(max(xs) - min(xs), max(ys) - min(ys))
|
|
if (
|
|
3 <= len(xs) <= 5
|
|
and len(uniquetol(xs, tol)) == 2
|
|
and len(uniquetol(ys, tol)) == 2
|
|
):
|
|
isrect = True
|
|
if isrect or el.tag == RECTANGLE_TAG:
|
|
sf = dh.get_strokefill(el)
|
|
hasfill = sf.fill is not None and sf.fill != [255, 255, 255, 1]
|
|
hasstroke = sf.stroke is not None and sf.stroke != [255, 255, 255, 1]
|
|
|
|
if hasfill and (not (hasstroke) or sf.stroke == sf.fill): # solid rectangle
|
|
solids[el.get_id()] = gbb
|
|
elif hasstroke: # framed rectangle
|
|
boxes[el.get_id()] = gbb
|
|
|
|
if el.get("inkscape-scientific-scaletype") == "plot_area":
|
|
plotareas[el.get_id()] = gbb
|
|
|
|
vels = dict()
|
|
hels = dict()
|
|
for k, gbb in vl.items():
|
|
vels[k] = gbb[3]
|
|
for k, gbb in hl.items():
|
|
hels[k] = gbb[2]
|
|
for k, gbb in boxes.items():
|
|
hels[k] = gbb[2]
|
|
vels[k] = gbb[3]
|
|
for k, gbb in plotareas.items():
|
|
hels[k] = gbb[2]
|
|
vels[k] = gbb[3]
|
|
|
|
lvel = lhel = None
|
|
if len(vels) != 0:
|
|
lvel = max(vels, key=vels.get) # largest vertical
|
|
if len(hels) != 0:
|
|
lhel = max(hels, key=hels.get) # largest horizontal
|
|
return vl, hl, lvel, lhel
|
|
|
|
|
|
# Get the proper suffix for an integer (1st, 2nd, 3rd, etc.)
|
|
def appendInt(num):
|
|
if num > 9:
|
|
secondToLastDigit = str(num)[-2]
|
|
if secondToLastDigit == "1":
|
|
return "th"
|
|
lastDigit = num % 10
|
|
if lastDigit == 1:
|
|
return "st"
|
|
elif lastDigit == 2:
|
|
return "nd"
|
|
elif lastDigit == 3:
|
|
return "rd"
|
|
else:
|
|
return "th"
|
|
|
|
|
|
def trtf(x, y):
|
|
return Transform("translate(" + str(x) + ", " + str(y) + ")")
|
|
|
|
|
|
def sctf(x, y):
|
|
return Transform("scale(" + str(x) + ", " + str(y) + ")")
|
|
|
|
|
|
class ScalePlots(inkex.EffectExtension):
|
|
def add_arguments(self, pars):
|
|
pars.add_argument(
|
|
"--hscale", type=float, default=100, help="Horizontal scaling"
|
|
)
|
|
pars.add_argument("--vscale", type=float, default=100, help="Vertical scaling")
|
|
pars.add_argument(
|
|
"--figuremode", type=int, default=1, help="Scale by bounding box?"
|
|
)
|
|
pars.add_argument("--matchprop", type=int, default=1, help="Match what?")
|
|
pars.add_argument(
|
|
"--hmatchopts", type=int, default=1, help="Horizontal matching"
|
|
)
|
|
pars.add_argument("--vmatchopts", type=int, default=1, help="Vertical matching")
|
|
pars.add_argument(
|
|
"--deletematch",
|
|
type=inkex.Boolean,
|
|
default=False,
|
|
help="Delete first selection?",
|
|
)
|
|
pars.add_argument("--marksf", type=int, default=1, help="Mark objects as")
|
|
pars.add_argument("--tab", help="The selected UI-tab when OK was pressed")
|
|
pars.add_argument(
|
|
"--tickcorrect", type=inkex.Boolean, default=True, help="Auto tick correct?"
|
|
)
|
|
pars.add_argument(
|
|
"--tickthreshold", type=int, default=10, help="Tick threshold"
|
|
)
|
|
pars.add_argument(
|
|
"--wholeplot1",
|
|
type=inkex.Boolean,
|
|
default=False,
|
|
help="Treat whole selection as plot area?",
|
|
)
|
|
pars.add_argument(
|
|
"--wholeplot2",
|
|
type=inkex.Boolean,
|
|
default=False,
|
|
help="Treat whole selection as plot area?",
|
|
)
|
|
pars.add_argument(
|
|
"--wholeplot3",
|
|
type=inkex.Boolean,
|
|
default=False,
|
|
help="Treat whole selection as plot area?",
|
|
)
|
|
|
|
def effect(self):
|
|
sel = [self.svg.selection[ii] for ii in range(len(self.svg.selection))]
|
|
sel = [k for k in sel if k.tag not in EXCLUDE_TAGS]
|
|
# regular selectable objects only
|
|
|
|
itag = inkex.Image.ctag
|
|
if all([el.tag == itag for el in sel]) and self.options.tab != "options":
|
|
inkex.utils.errormsg(
|
|
"Thanks for using Scientific Inkscape!\n\n"
|
|
"It appears that you're attempting to scale a raster Image object. Please note that "
|
|
"Inkscape is mainly for working with vector images, not raster images. Vector images "
|
|
"preserve all of the information used to generate them, whereas raster images do not. "
|
|
"Read about the difference here: \nhttps://en.wikipedia.org/wiki/Vector_graphics\n\n"
|
|
"Unfortunately, this means that there is not much Scale Plots can do to edit raster images "
|
|
"beyond simple stretching or scaling. If you want to edit a raster image, you will need to "
|
|
"use a program like Photoshop or GIMP."
|
|
)
|
|
quit()
|
|
|
|
tickcorrect = self.options.tickcorrect
|
|
tickthr = self.options.tickthreshold / 100
|
|
# layerfix = self.options.layerfix
|
|
if self.options.tab == "scaling":
|
|
hscale = self.options.hscale
|
|
vscale = self.options.vscale
|
|
scalex = hscale / 100
|
|
scaley = vscale / 100
|
|
wholesel = self.options.wholeplot1
|
|
elif self.options.tab == "matching":
|
|
hmatch = self.options.hmatchopts in [2, 3]
|
|
vmatch = self.options.vmatchopts in [2, 3]
|
|
matchxpos = self.options.hmatchopts == 3
|
|
matchypos = self.options.vmatchopts == 3
|
|
wholesel = self.options.wholeplot2
|
|
elif self.options.tab == "correction":
|
|
scalex = 1
|
|
scaley = 1
|
|
wholesel = self.options.wholeplot3
|
|
if not all(k.tag == GROUP_TAG for k in sel):
|
|
inkex.utils.errormsg(
|
|
"Correction mode requires that every selected object be a group that has already been scaled."
|
|
)
|
|
|
|
return
|
|
else:
|
|
self.options.marksf = {
|
|
1: "scale_free",
|
|
2: "aspect_locked",
|
|
3: "normal",
|
|
4: "plot_area",
|
|
5: None,
|
|
}[self.options.marksf]
|
|
for el in sel:
|
|
el.set("inkscape-scientific-scaletype", self.options.marksf)
|
|
return
|
|
self.options.matchwhat = {1: "plotarea", 2: "bbox"}[self.options.matchprop]
|
|
self.options.matchto = {1: "plotarea", 2: "bbox"}[self.options.matchprop]
|
|
self.options.figuremode = {1: False, 2: True}[self.options.figuremode]
|
|
|
|
if wholesel:
|
|
tickcorrect = False
|
|
|
|
dsfels = [] # designated scale-free els, whether or not they're selected
|
|
|
|
# full visual bbs
|
|
fbbs = dh.BB2(self.svg, dh.unique([d for el in sel for d in el.descendants2()]))
|
|
firstsel = sel[0]
|
|
if self.options.tab == "matching":
|
|
sel = sel[1:]
|
|
|
|
all_pels = [sel]
|
|
if all(k.tag == GROUP_TAG for k in sel): # grouped mode
|
|
trs = [s.ctransform for s in sel]
|
|
# for correction mode
|
|
all_pels = [list(s) for s in sel]
|
|
|
|
for i0 in range(len(all_pels)): # sel in asel:
|
|
pels = [
|
|
k for k in all_pels[i0] if k.get_id() in list(fbbs.keys())
|
|
] # plot elements list
|
|
|
|
# Calculate geometric (tight) bounding boxes of plot elements
|
|
gbbs = dict()
|
|
for el in [firstsel] + pels + dsfels + list(firstsel):
|
|
if el.get_id() in fbbs:
|
|
gbbs[el.get_id()] = geometric_bbox(el, fbbs[el.get_id()]).sbb
|
|
|
|
vl, hl, lvel, lhel = Find_Plot_Area(pels, gbbs)
|
|
if lvel is None or lhel is None or wholesel:
|
|
noplotarea = True
|
|
lvel = None
|
|
lhel = None
|
|
if not (wholesel):
|
|
# Display warning message
|
|
numgroup = str(i0 + 1) + appendInt(i0 + 1)
|
|
inkex.utils.errormsg(
|
|
"A box-like plot area could not be automatically detected on the "
|
|
+ numgroup
|
|
+ " selected plot (group ID "
|
|
+ sel[i0].get_id()
|
|
+ ").\n\nDraw a box with a stroke to define the plot area or mark objects as plot area-determining in the Advanced tab."
|
|
+ "\nScaling will still be performed, but the results may not be ideal."
|
|
)
|
|
else:
|
|
noplotarea = False
|
|
|
|
# A class that contains geometric and full bounding boxes
|
|
class bbox2:
|
|
def __init__(self, g, f):
|
|
if isinstance(g, list) or g is None:
|
|
g = bbox(g)
|
|
if isinstance(f, list) or f is None:
|
|
f = bbox(f)
|
|
self.g = g
|
|
self.f = f
|
|
|
|
def union(self, g, f):
|
|
return bbox2(self.g.union(g), self.f.union(f))
|
|
|
|
bba = bbox2(None, None)
|
|
# all elements
|
|
bbp = bbox2(None, None)
|
|
# plot area
|
|
for el in pels:
|
|
bba = bba.union(gbbs[el.get_id()], fbbs[el.get_id()])
|
|
if el.get_id() in [lvel, lhel] or noplotarea:
|
|
bbp = bbp.union(gbbs[el.get_id()], fbbs[el.get_id()])
|
|
|
|
if self.options.tab == "correction":
|
|
# Invert the existing transform so we can run the rest of the code regularly
|
|
extr = trs[i0]
|
|
# existing transform
|
|
|
|
sx = math.sqrt(extr.a**2 + extr.b**2)
|
|
sy = (-extr.b * extr.c + extr.a * extr.d) / math.sqrt(
|
|
extr.a**2 + extr.b**2
|
|
)
|
|
if sx < 0:
|
|
sx = -sx
|
|
sy = -sy
|
|
scalex = sx
|
|
scaley = sy
|
|
# allow for rotations
|
|
|
|
if not (self.options.figuremode):
|
|
refx = bbp.g.xc
|
|
refy = bbp.g.yc
|
|
else:
|
|
refx = bba.f.x1
|
|
refy = bba.f.y1
|
|
trl = trtf(refx, refy)
|
|
scl = sctf(1 / scalex, 1 / scaley)
|
|
iextr = trl @ scl @ (-trl)
|
|
# invert existing transform
|
|
dh.global_transform(sel[i0], iextr)
|
|
|
|
# Invert the transform on the bounding boxes (fix later)
|
|
actfbbs = {k: v for k, v in fbbs.items()}
|
|
actgbbs = {k: v for k, v in gbbs.items()}
|
|
fbbs = {k: bbox(v).transform(iextr).sbb for k, v in fbbs.items()}
|
|
gbbs = {k: bbox(v).transform(iextr).sbb for k, v in gbbs.items()}
|
|
tr_bba = bba
|
|
# bb with transform to be corrected
|
|
|
|
bba = bbox2(None, None)
|
|
# bbox of all elements
|
|
bbp = bbox2(None, None)
|
|
# bbox of plot area
|
|
for el in pels:
|
|
bba = bba.union(gbbs[el.get_id()], fbbs[el.get_id()])
|
|
if el.get_id() in [lvel, lhel] or noplotarea:
|
|
bbp = bbp.union(gbbs[el.get_id()], fbbs[el.get_id()])
|
|
|
|
if self.options.figuremode:
|
|
oscalex = scalex
|
|
oscaley = scaley
|
|
scalex = (
|
|
(tr_bba.f.x2 - tr_bba.f.x1)
|
|
- (bba.f.x2 - bba.f.x1 - (bbp.g.x2 - bbp.g.x1))
|
|
) / (bbp.g.x2 - bbp.g.x1)
|
|
scaley = (
|
|
(tr_bba.f.y2 - tr_bba.f.y1)
|
|
- (bba.f.y2 - bba.f.y1 - (bbp.g.y2 - bbp.g.y1))
|
|
) / (bbp.g.y2 - bbp.g.y1)
|
|
|
|
tlx = (tr_bba.f.x1 - refx) / oscalex + refx
|
|
# where top left is now
|
|
dxl = bbp.g.x1 - tlx
|
|
if scalex != 1:
|
|
refx = (tr_bba.f.x1 + dxl - bbp.g.x1 * scalex) / (1 - scalex)
|
|
# what refx needs to be to maintain top-left
|
|
else:
|
|
refx = tr_bba.f.x1 + dxl
|
|
|
|
tly = (tr_bba.f.y1 - refy) / oscaley + refy
|
|
# where top left is now
|
|
dyl = bbp.g.y1 - tly
|
|
if scaley != 1:
|
|
refy = (tr_bba.f.y1 + dyl - bbp.g.y1 * scaley) / (1 - scaley)
|
|
# what refx needs to be to maintain top-left
|
|
else:
|
|
refy = tr_bba.f.y1 + dyl
|
|
|
|
if self.options.tab == "matching":
|
|
bbmatch = None
|
|
if self.options.matchto == "bbox":
|
|
bbmatch = bbox(gbbs[firstsel.get_id()])
|
|
elif self.options.matchto == "plotarea":
|
|
if firstsel.tag == GROUP_TAG:
|
|
plotareaels = list(firstsel)
|
|
else:
|
|
plotareaels = [firstsel]
|
|
|
|
vl0, hl0, lvel0, lhel0 = Find_Plot_Area(plotareaels, gbbs)
|
|
if lvel0 is None or lhel0 is None:
|
|
inkex.utils.errormsg(
|
|
"A box-like plot area could not be automatically detected on the "
|
|
+ "first selected object (ID "
|
|
+ firstsel.get_id()
|
|
+ ").\n\nIts bounding box will be matched instead. If this is not ideal,"
|
|
+ " draw an outlined box to define the plot area or mark objects as plot area-determining in the Advanced tab.\n"
|
|
)
|
|
bbmatch = bbox(gbbs[firstsel.get_id()])
|
|
else:
|
|
bbmatch = bbox(gbbs[lvel0]).union(bbox(gbbs[lhel0]))
|
|
|
|
scalex = scaley = 1
|
|
if hmatch:
|
|
if self.options.matchwhat == "plotarea":
|
|
scalex = bbmatch.w / bbp.g.w
|
|
elif self.options.matchwhat == "bbox":
|
|
scalex = (bbmatch.w + bbp.g.w - bba.g.w) / bbp.g.w
|
|
if vmatch:
|
|
if self.options.matchwhat == "plotarea":
|
|
scaley = bbmatch.h / bbp.g.h
|
|
elif self.options.matchwhat == "bbox":
|
|
scaley = (bbmatch.h + bbp.g.h - bba.g.h) / bbp.g.h
|
|
|
|
# Compute global transformation
|
|
if self.options.tab != "correction":
|
|
if self.options.matchwhat == 'plotarea':
|
|
refx = bbp.g.xc
|
|
refy = bbp.g.yc
|
|
else:
|
|
refx = bba.g.xc
|
|
refy = bba.g.yc
|
|
|
|
finx = refx # final x
|
|
finy = refy # final y
|
|
if self.options.tab == "matching":
|
|
if matchxpos:
|
|
finx = bbmatch.xc
|
|
if matchypos:
|
|
finy = bbmatch.yc
|
|
if self.options.matchwhat == "bbox":
|
|
# Following scaling, margins stay the same size so the
|
|
# bounding box center has moved
|
|
finx -= 1/2*( (bba.g.x2 - bbp.g.x2) - (bbp.g.x1 - bba.g.x1)) * (1-scalex)
|
|
finy -= 1/2*( (bba.g.y2 - bbp.g.y2) - (bbp.g.y1 - bba.g.y1)) * (1-scaley)
|
|
|
|
gtr = trtf(finx, finy) @ sctf(scalex, scaley) @ (-trtf(refx, refy))
|
|
# global transformation
|
|
iscl = sctf(1 / scalex, 1 / scaley) # inverse scale
|
|
liscl = (
|
|
sctf(math.sqrt(scalex * scaley), math.sqrt(scalex * scaley)) @ iscl
|
|
) # aspect-scaled and inverse scaled
|
|
trul = gtr.apply_to_point([bbp.g.x1, bbp.g.y1]) # transformed upper-left
|
|
trbr = gtr.apply_to_point([bbp.g.x2, bbp.g.y2]) # transformed bottom-right
|
|
|
|
# Diagnostic mode
|
|
diagmode = False
|
|
if diagmode:
|
|
r = inkex.Rectangle()
|
|
r.set("x", bbp.g.x1)
|
|
r.set("y", bbp.g.y1)
|
|
r.set("width", abs(bbp.g.x2 - bbp.g.x1))
|
|
r.set("height", abs(bbp.g.y2 - bbp.g.y1))
|
|
r.cstyle = "fill-opacity:0.5"
|
|
self.svg.append(r)
|
|
dh.global_transform(r, gtr)
|
|
dh.debug("Largest vertical line: " + lvel)
|
|
dh.debug("Largest horizontal line: " + lhel)
|
|
|
|
# Make a list of elements to be transformed
|
|
sclels = []
|
|
for el in pels:
|
|
if el not in sclels:
|
|
sclels.append(el)
|
|
|
|
# Apply transform and compute corrections (if needed)
|
|
for el in sclels:
|
|
dh.global_transform(el, gtr)
|
|
# apply the transform
|
|
|
|
elid = el.get_id()
|
|
gbb = gbbs[elid]
|
|
fbb = fbbs[elid]
|
|
|
|
if el.tag in TEXTLIKE_TAGS + [GROUP_TAG] or el in dsfels:
|
|
stype = "scale_free"
|
|
else:
|
|
stype = "normal"
|
|
|
|
mtype = el.get("inkscape-scientific-scaletype")
|
|
if mtype is not None:
|
|
stype = mtype
|
|
|
|
if hasattr(self.options, "hcall") and self.options.hcall:
|
|
if el.tag in TEXTLIKE_TAGS + [GROUP_TAG]:
|
|
stype = "normal"
|
|
|
|
vtickt = vtickb = htickl = htickr = False
|
|
# el is a tick
|
|
if tickcorrect and (
|
|
(elid in list(vl.keys())) or (elid in list(hl.keys()))
|
|
):
|
|
isvert = elid in list(vl.keys())
|
|
ishorz = elid in list(hl.keys())
|
|
gbb = gbbs[elid]
|
|
if isvert and gbb[3] < tickthr * (
|
|
bbp.g.y2 - bbp.g.y1
|
|
): # vertical tick
|
|
if gbb[1] + gbb[3] < bbp.g.y1 + tickthr * (bbp.g.y2 - bbp.g.y1):
|
|
vtickt = True
|
|
elif gbb[1] > bbp.g.y2 - tickthr * (bbp.g.y2 - bbp.g.y1):
|
|
vtickb = True
|
|
if ishorz and gbb[2] < tickthr * (
|
|
bbp.g.x2 - bbp.g.x1
|
|
): # horizontal tick
|
|
if gbb[0] + gbb[2] < bbp.g.x1 + tickthr * (bbp.g.x2 - bbp.g.x1):
|
|
htickl = True
|
|
elif gbb[0] > bbp.g.x2 - tickthr * (bbp.g.x2 - bbp.g.x1):
|
|
htickr = True
|
|
|
|
if any([vtickt, vtickb, htickl, htickr]):
|
|
# If a tick, scale using the edge as a reference point
|
|
gbb_tr = bbox(gbb).transform(gtr)
|
|
cx = gbb_tr.xc
|
|
cy = gbb_tr.yc
|
|
|
|
if vtickt:
|
|
if cy > trul[1]:
|
|
trl = trtf(cx, gbb_tr.y1) # inner tick
|
|
else:
|
|
trl = trtf(cx, gbb_tr.y2) # outer tick
|
|
elif vtickb:
|
|
if cy < trbr[1]:
|
|
trl = trtf(cx, gbb_tr.y2) # inner tick
|
|
else:
|
|
trl = trtf(cx, gbb_tr.y1) # outer tick
|
|
elif htickl:
|
|
if cx > trul[0]:
|
|
trl = trtf(gbb_tr.x1, cy) # inner tick
|
|
else:
|
|
trl = trtf(gbb_tr.x2, cy) # outer tick
|
|
elif htickr:
|
|
if cx < trbr[0]:
|
|
trl = trtf(gbb_tr.x2, cy) # inner tick
|
|
else:
|
|
trl = trtf(gbb_tr.x1, cy) # outer tick
|
|
tr1 = trl @ iscl @ (-trl)
|
|
dh.global_transform(el, tr1)
|
|
# elif isalwayscorr or isoutsideplot or issf:
|
|
elif stype in ["scale_free", "aspect_locked"]:
|
|
# dh.idebug(el.get_id())
|
|
# Invert the transformation for text/groups, anything outside the plot, scale-free
|
|
cbc = el.get("inkscape-scientific-combined-by-color")
|
|
if cbc is None:
|
|
gbb_tr = bbox(gbb).transform(gtr)
|
|
cx = gbb_tr.xc
|
|
cy = gbb_tr.yc
|
|
trl = trtf(cx, cy)
|
|
if stype == "scale_free":
|
|
tr1 = trl @ iscl @ (-trl)
|
|
else:
|
|
tr1 = trl @ liscl @ (-trl)
|
|
|
|
# For elements outside the plot area, adjust position to maintain
|
|
# the distance to the plot area
|
|
dx = 0
|
|
dy = 0
|
|
if cx < trul[0]:
|
|
ox = gbb[0] + gbb[2] / 2 - bbp.g.x1
|
|
dx = ox - (cx - trul[0])
|
|
if cx > trbr[0]:
|
|
ox = gbb[0] + gbb[2] / 2 - bbp.g.x2
|
|
dx = ox - (cx - trbr[0])
|
|
if cy < trul[1]:
|
|
oy = gbb[1] + gbb[3] / 2 - bbp.g.y1
|
|
dy = oy - (cy - trul[1])
|
|
if cy > trbr[1]:
|
|
oy = gbb[1] + gbb[3] / 2 - bbp.g.y2
|
|
dy = oy - (cy - trbr[1])
|
|
tr2 = trtf(dx, dy)
|
|
dh.global_transform(el, (tr2 @ tr1))
|
|
|
|
else: # If previously combined, apply to subpaths instead
|
|
cbc = [int(v) for v in cbc.split()]
|
|
fbb_tr = bbox(fbb).transform(gtr).sbb
|
|
irng = []
|
|
trng = []
|
|
for ii in range(len(cbc) - 1):
|
|
gbb_tr = geometric_bbox(
|
|
el, fbb_tr, irange=[cbc[ii], cbc[ii + 1]]
|
|
)
|
|
gbb = gbb_tr.transform(-gtr).sbb
|
|
cx = gbb_tr.xc
|
|
cy = gbb_tr.yc
|
|
trl = trtf(cx, cy)
|
|
# tr1 = trl @ iscl @ (-trl)
|
|
if stype == "scale_free":
|
|
tr1 = trl @ iscl @ (-trl)
|
|
else:
|
|
tr1 = trl @ liscl @ (-trl)
|
|
dx = 0
|
|
dy = 0
|
|
if cx < trul[0]:
|
|
ox = gbb[0] + gbb[2] / 2 - bbp.g.x1
|
|
dx = ox - (cx - trul[0])
|
|
if cx > trbr[0]:
|
|
ox = gbb[0] + gbb[2] / 2 - bbp.g.x2
|
|
dx = ox - (cx - trbr[0])
|
|
if cy < trul[1]:
|
|
oy = gbb[1] + gbb[3] / 2 - bbp.g.y1
|
|
dy = oy - (cy - trul[1])
|
|
if cy > trbr[1]:
|
|
oy = gbb[1] + gbb[3] / 2 - bbp.g.y2
|
|
dy = oy - (cy - trbr[1])
|
|
tr2 = trtf(dx, dy)
|
|
irng.append([cbc[ii], cbc[ii + 1]])
|
|
trng.append((tr2 @ tr1))
|
|
dh.global_transform(el, It, irange=irng, trange=trng)
|
|
|
|
# restore bbs
|
|
if self.options.tab == "correction":
|
|
fbbs = actfbbs
|
|
gbbs = actgbbs
|
|
|
|
if self.options.tab == "matching" and self.options.deletematch:
|
|
firstsel.delete()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
dh.Run_SI_Extension(ScalePlots(), "Scale plots")
|