From c86a745be677cc26dd17e431196ff4ff94055636 Mon Sep 17 00:00:00 2001 From: Scott Wittenburg Date: Wed, 13 Feb 2019 15:43:24 -0700 Subject: [PATCH] Add support for the 'reference' attribute on the vector gm The 'reference' attribute has been available to read/write on the vector graphics method, but until now has not been implemented. Now, if it is set, the default behavior of choosing a sensible value for the arrow length based on the screen space availabe, text size, etc., will be overridden. Instead the arrow length will simply be chosen to match the reference attribute. Hence, this feature should be used with care, as it could result in a vector legend with either very large or very small reference arrow. --- tests/test_vcs_vectors_scale.py | 10 +++++- vcs/utils.py | 59 ++++++++++++++++++++++----------- vcs/vcsvtk/vectorpipeline.py | 2 +- vcs/vector.py | 7 ++-- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/tests/test_vcs_vectors_scale.py b/tests/test_vcs_vectors_scale.py index 3ced33b3d..c7a0630da 100644 --- a/tests/test_vcs_vectors_scale.py +++ b/tests/test_vcs_vectors_scale.py @@ -6,7 +6,7 @@ class TestVCSVectorScale(basevcstest.VCSBaseTest): - def coreTest(self, scalingType, scale=None, scalerange=None): + def coreTest(self, scalingType, scale=None, scalerange=None, ref=None): u = cdms2.createAxis(np.array([[1, 1, 0, 1, 1, 1, 0, 1], [1, 1, 0, 1, 1, 1, 0, 1], [1, 1, 0, 1, 1, 1, 0, 1], @@ -32,11 +32,15 @@ def coreTest(self, scalingType, scale=None, scalerange=None): gv.scalerange = scalerange if scale is not None: gv.scale = scale + if ref is not None: + gv.reference = ref self.x.plot(u, v, gv, bg=self.bg, ratio=1) label = scalingType if (scalingType == "constant"): label += "_" + str(scale) outFilename = 'test_vcs_vectors_scale_%s.png' % label + if ref is not None: + outFilename = 'test_vcs_vectors_scale_%s_ref_%s.png' % (label, ref) self.checkImage(outFilename) self.x.clear() @@ -50,4 +54,8 @@ def testVCSVectorScalingOptions(self): # test clamping self.coreTest("constant", scale=0.1) self.coreTest("constant", scale=2) + # test reference + self.coreTest("constant", scale=0.5, ref=1) + self.coreTest("constant", scale=0.1, ref=5) + self.coreTest("constant", scale=2, ref=0.5) testVCSVectorScalingOptions.vectors = 1 diff --git a/vcs/utils.py b/vcs/utils.py index 30e9bb6c0..da2e10f0a 100644 --- a/vcs/utils.py +++ b/vcs/utils.py @@ -2838,7 +2838,8 @@ def _createLegendString(value, unit): def drawVectorLegend(canvas, templateLegend, linecolor, linetype, linewidth, unitString, maxNormInVp=1., maxNorm=1., - minNormInVp=0., minNorm=0., bg=False, render=True): + minNormInVp=0., minNorm=0., bg=False, render=True, + reference=1e20): """Draws a legend with vector line/text inside a template legend box Auto adjust text size to make it fit inside the box @@ -2895,14 +2896,29 @@ def drawVectorLegend(canvas, templateLegend, :param render: Boolean value indicating whether or not to render the new lines. :type render: `bool`_ + + : param reference: Desired length of reference vector in plot legend. The + default is to choose a reasonable size for the max vector in the legend + based on the amount of space available. This behavior can be + overridden by providing the "reference" parameter, and then the size of + the arrow will be computed to match. Be aware this may cause the arrow + to be very large (not fitting nicely within the legend) or very small, + even invisible. + : type reference: `float`_ """ + print('drawVectorLegend, maxNormInVp = {0}, maxNorm = {1}, reference = {2}'.format(maxNormInVp, maxNorm, reference)) + + useReferenceValue = not numpy.allclose(reference, 1e20) + # Figure out space length text = vcs.createtext(To_source=templateLegend.textorientation, Tt_source=templateLegend.texttable) text.x = .5 text.y = .5 maxLegendString = _createLegendString(maxNorm, unitString) + if useReferenceValue: + maxLegendString = _createLegendString(reference, unitString) text.string = maxLegendString maxExt = canvas.gettextextent(text)[0] @@ -2912,27 +2928,30 @@ def drawVectorLegend(canvas, templateLegend, # space between line and label - one character long spaceLength = (maxExt[1] - maxExt[0]) / len(maxLegendString) - # line vector - min 2 and max 15 characters long - minMaxNormLineLength = 2 * spaceLength - maxMaxNormLineLength = 15 * spaceLength - # clamp lineLegth between 2 and 15 spaceLength maxLineLength = maxNormInVp + if useReferenceValue: + maxLineLength = (maxNormInVp * reference) / maxNorm + else: + # line vector - min 2 and max 15 characters long + minMaxNormLineLength = 2 * spaceLength + maxMaxNormLineLength = 15 * spaceLength + # clamp lineLegth between 2 and 15 spaceLength + ratio = 1.0 + if (maxLineLength < minMaxNormLineLength): + while (maxLineLength < minMaxNormLineLength): + maxLineLength *= 2 + ratio *= 2 + elif (maxLineLength > maxMaxNormLineLength): + while (maxLineLength > maxMaxNormLineLength): + maxLineLength /= 2 + ratio /= 2 + + # update maxLegendString with the clamped value + if (ratio != 1): + maxLegendString = _createLegendString(maxNorm * ratio, unitString) + text.string = maxLegendString + maxExt = canvas.gettextextent(text)[0] minLineLength = minNormInVp - ratio = 1.0 - if (maxLineLength < minMaxNormLineLength): - while (maxLineLength < minMaxNormLineLength): - maxLineLength *= 2 - ratio *= 2 - elif (maxLineLength > maxMaxNormLineLength): - while (maxLineLength > maxMaxNormLineLength): - maxLineLength /= 2 - ratio /= 2 - - # update maxLegendString with the clamped value - if (ratio != 1): - maxLegendString = _createLegendString(maxNorm * ratio, unitString) - text.string = maxLegendString - maxExt = canvas.gettextextent(text)[0] maxLegendLength = maxExt[1] - maxExt[0] maxheight = maxExt[3] - maxExt[2] diff --git a/vcs/vcsvtk/vectorpipeline.py b/vcs/vcsvtk/vectorpipeline.py index e1133abbd..b9204406b 100644 --- a/vcs/vcsvtk/vectorpipeline.py +++ b/vcs/vcsvtk/vectorpipeline.py @@ -245,7 +245,7 @@ def _plotInternal(self): minNormInVp *= worldToViewportXScale vcs.utils.drawVectorLegend( self._context().canvas, self._template.legend, lcolor, lstyle, lwidth, - unitString, maxNormInVp, maxNorm, minNormInVp, minNorm) + unitString, maxNormInVp, maxNorm, minNormInVp, minNorm, reference=self._gm.reference) kwargs['xaxisconvert'] = self._gm.xaxisconvert kwargs['yaxisconvert'] = self._gm.yaxisconvert diff --git a/vcs/vector.py b/vcs/vector.py index d98ae4c67..0b0c80f58 100755 --- a/vcs/vector.py +++ b/vcs/vector.py @@ -308,7 +308,10 @@ class Gv(vcs.bestMatch): .. code-block:: python - # Can be an integer or float + # Can be an integer or float. Setting the reference attribute + # overrides the default behavior of picking a reasonable size + # for the vector legend arrow. This may result in a very large + # or very small arrow, depending on the value of vc.reference. vc.reference=4 """ __slots__ = [ @@ -772,7 +775,7 @@ def list(self): print("datawc_calendar = ", self.datawc_calendar) print("xaxisconvert = ", self.xaxisconvert) print("yaxisconvert = ", self.yaxisconvert) - print("line = ", self.line) + print("linetype = ", self.linetype) print("linecolor = ", self.linecolor) print("linewidth = ", self.linewidth) print("scale = ", self.scale)