From 8fa7f2451bbfb2fb0323bbd49abd4d714c40b039 Mon Sep 17 00:00:00 2001
From: Recolic Keghart <root@recolic.net>
Date: Mon, 18 Dec 2017 19:56:19 +0800
Subject: [PATCH] New function added: Generate Multi Lines on single figure.

---
 TuningFork/draw.py              |  8 ++--
 TuningFork/others_data/tmp1.dat | 41 +++++++++++++++++
 TuningFork/others_data/tmp2.dat | 30 +++++++++++++
 deprecated.py                   | 78 +++++++++++++++++++++++++++++++++
 quickmap.py                     | 78 ++++++++++++++++++++++-----------
 5 files changed, 206 insertions(+), 29 deletions(-)
 create mode 100644 TuningFork/others_data/tmp1.dat
 create mode 100644 TuningFork/others_data/tmp2.dat
 create mode 100644 deprecated.py

diff --git a/TuningFork/draw.py b/TuningFork/draw.py
index ae325a8..a1d1f90 100755
--- a/TuningFork/draw.py
+++ b/TuningFork/draw.py
@@ -3,12 +3,12 @@
 import sys
 sys.path.append('..')
 import quickmap
+from quickmap import GetMultiMap, GetLine
 
-x,y = quickmap.DataFileToXYArray('without_damping.dat')
-quickmap.GetMap(x,y,smoothLine=True)
+x1,y1 = quickmap.DataFileToXYArray('without_damping.dat')
+x2,y2 = quickmap.DataFileToXYArray('with_damping.dat')
 
-x,y = quickmap.DataFileToXYArray('with_damping.dat')
-quickmap.GetMap(x,y,smoothLine=True)
+GetMultiMap(GetLine(x1, y1, smoothLine=True) + GetLine(x2, y2, smoothLine=True))
 
 sq_T = '14.633 19.327 24.256 29.192 34.589 39.547'.split(' ')
 sq_T = [float(e) for e in sq_T]
diff --git a/TuningFork/others_data/tmp1.dat b/TuningFork/others_data/tmp1.dat
new file mode 100644
index 0000000..3d8c92b
--- /dev/null
+++ b/TuningFork/others_data/tmp1.dat
@@ -0,0 +1,41 @@
+# f(Hz) u
+
+260.048 0.059
+260.348 0.068
+260.648 0.081
+260.948 0.102
+261.248 0.136
+261.448 0.178
+261.648 0.260
+261.748 0.341
+261.798 0.403
+261.848 0.491
+261.898 0.629
+261.928 0.744
+261.948 0.863
+261.958 0.920
+261.968 0.999
+261.978 1.060
+261.988 1.154
+261.998 1.226
+262.008 1.321
+262.018 1.404
+262.028 1.452
+262.038 1.492
+262.048 1.500
+262.058 1.482
+262.068 1.434
+262.078 1.382
+262.088 1.306
+262.098 1.244
+262.118 1.103
+262.128 1.027
+262.228 0.570
+262.328 0.376
+262.428 0.277
+262.738 0.187
+263.548 0.075
+263.798 0.059
+264.048 0.043
+
+
diff --git a/TuningFork/others_data/tmp2.dat b/TuningFork/others_data/tmp2.dat
new file mode 100644
index 0000000..2b90708
--- /dev/null
+++ b/TuningFork/others_data/tmp2.dat
@@ -0,0 +1,30 @@
+259.774 0.057
+260.074 0.065
+260.374 0.078
+260.674 0.097
+260.974 0.130
+261.274 0.201
+261.474 0.323
+261.574 0.464
+261.625 0.589
+261.675 0.793
+261.705 0.961
+261.715 1.034
+261.725 1.112
+261.735 1.167
+261.744 1.218
+261.754 1.267
+261.764 1.298
+261.774 1.305
+261.784 1.290
+261.794 1.268
+261.804 1.220
+261.814 1.179
+262.114 0.293
+262.314 0.182
+262.514 0.129
+262.814 0.089
+263.214 0.061
+263.414 0.052
+263.714 0.042
+263.774 0.041
diff --git a/deprecated.py b/deprecated.py
new file mode 100644
index 0000000..d22f884
--- /dev/null
+++ b/deprecated.py
@@ -0,0 +1,78 @@
+import functools
+import inspect
+import warnings
+
+string_types = (type(b''), type(u''))
+
+
+def deprecated(reason):
+    """
+    This is a decorator which can be used to mark functions
+    as deprecated. It will result in a warning being emitted
+    when the function is used.
+    """
+
+    if isinstance(reason, string_types):
+
+        # The @deprecated is used with a 'reason'.
+        #
+        # .. code-block:: python
+        #
+        #    @deprecated("please, use another function")
+        #    def old_function(x, y):
+        #      pass
+
+        def decorator(func1):
+
+            if inspect.isclass(func1):
+                fmt1 = "Call to deprecated class {name} ({reason})."
+            else:
+                fmt1 = "Call to deprecated function {name} ({reason})."
+
+            @functools.wraps(func1)
+            def new_func1(*args, **kwargs):
+                warnings.simplefilter('always', DeprecationWarning)
+                warnings.warn(
+                    fmt1.format(name=func1.__name__, reason=reason),
+                    category=DeprecationWarning,
+                    stacklevel=2
+                )
+                warnings.simplefilter('default', DeprecationWarning)
+                return func1(*args, **kwargs)
+
+            return new_func1
+
+        return decorator
+
+    elif inspect.isclass(reason) or inspect.isfunction(reason):
+
+        # The @deprecated is used without any 'reason'.
+        #
+        # .. code-block:: python
+        #
+        #    @deprecated
+        #    def old_function(x, y):
+        #      pass
+
+        func2 = reason
+
+        if inspect.isclass(func2):
+            fmt2 = "Call to deprecated class {name}."
+        else:
+            fmt2 = "Call to deprecated function {name}."
+
+        @functools.wraps(func2)
+        def new_func2(*args, **kwargs):
+            warnings.simplefilter('always', DeprecationWarning)
+            warnings.warn(
+                fmt2.format(name=func2.__name__),
+                category=DeprecationWarning,
+                stacklevel=2
+            )
+            warnings.simplefilter('default', DeprecationWarning)
+            return func2(*args, **kwargs)
+
+        return new_func2
+
+    else:
+        raise TypeError(repr(type(reason)))
\ No newline at end of file
diff --git a/quickmap.py b/quickmap.py
index b42dc05..748ff0a 100644
--- a/quickmap.py
+++ b/quickmap.py
@@ -9,6 +9,8 @@ import matplotlib.pyplot as plt
 from matplotlib import rcParams
 from scipy.interpolate import spline
 
+from deprecated import deprecated
+
 def dotMultiply(vctA, vctB):
     if len(vctA) != len(vctB):
         print("Error: While vcta is ", vctA, " and vctb is ", vctB)
@@ -18,16 +20,54 @@ def dotMultiply(vctA, vctB):
         ans += a * b
     return ans
 
-def GetMap(arrX, arrY, windowSizeX=12, windowSizeY=8, extendXRate=1, extendYRate=1, polyLine=False, poly_passO=False, poly_maxXPower=1, poly_inverseK=False, smoothLine=False):
+def __fetchAnonymousLineName():
+    for i in range(10000):
+        yield 'Unnamed Line ' + str(i)
+_fetchAnonymousLineName = __fetchAnonymousLineName()
+fetchAnonymousLineName = lambda :next(_fetchAnonymousLineName)
+
+@deprecated("Use GetMultiMap(GetLine(...)) rather than GetMap() please!")
+def GetMap(arrX, arrY, extendXRate=1, extendYRate=1, polyLine=False, poly_passO=False, poly_maxXPower=1, poly_inverseK=False, smoothLine=False):
+    '''
+    A wrapper to GetMultiMap(GetLine(args ...)), just check doc in GetLine().
+    '''
+    GetMultiMap(GetLine(arrX, arrY, fetchAnonymousLineName(), extendXRate, extendYRate, polyLine, poly_passO, poly_maxXPower, poly_inverseK, smoothLine))
+
+def GetMultiMap(lines, windowSizeX=12, windowSizeY=8):
+    '''
+    Usage: GetMultiMap(GetLine([1,2,3], [1,4,9]) + GetLine([2,2.1,2.2], [6,7,8]) + ...)
+    '''
+    colors = ['red', 'orange', 'blue', 'green', 'yellow', 'magenta', 'cyan', 'black'] # You can add more color like: '#123456', '#FFFFAE', etc
+    def __fetchColor():
+        for color in colors:
+            yield color
+        raise RuntimeError('Pre-defined colors are used out...... Check quickmap.py::GetMultiMap line 29 to add more color.')
+    _fetchColor = __fetchColor()
+    fetchColor = lambda :next(_fetchColor)
+    
+    plt.figure(figsize=(windowSizeX, windowSizeY))
+    for line in lines:
+        name, X, Y, px, py = line
+        plt.scatter(X, Y, color=fetchColor(), label="Sample Point ({})".format(name), linewidth=3)
+        if len(px) != len(py):
+            raise ValueError('Invalid line passed to GetMultiMap. Assertion len(px) == len(py) failed because {} != {}.'.format(len(px), len(py)))
+        if len(px) != 0:
+            plt.plot(px, py, color=fetchColor(), label="Fitting Line ({})".format(name), linewidth=2)
+    plt.grid()
+    plt.show()
+
+
+def GetLine(arrX, arrY, name=fetchAnonymousLineName(), extendXRate=1, extendYRate=1, polyLine=False, poly_passO=False, poly_maxXPower=1, poly_inverseK=False, smoothLine=False):
     '''
     Arguments:
-    arrX and arrY: array of coordinates of points. Ex: GetMap([1,2,3,4,5], [1,2,3,4,5]) -> y=x
+    arrX and arrY: array of coordinates of points. Ex: GetLine([1,2,3,4,5], [1,2,3,4,5]) -> y=x
+    name: Shown in GetMultiMap().
     polyLine(conflict with polyLine): Should I draw a fitting line by polynomial fitting?
-    smoothLine(conflict with smoothLine): Should I draw a fitting line by connecting all points and smooth them?
+    smoothLine(conflict with smoothLine, **RECOMMENDED**): Should I draw a fitting line by connecting all points and smooth them?
     passO: Should the fitting line pass (0,0)? That's saying, should k0 be zero?
-    maxXPower: If I should draw a fitting line, what polynomial function should I use? Ex: GetMap([1,2,3,4,5], [1,4,9,16,25], line=True, maxXPower) -> y=x^2
+    maxXPower: If I should draw a fitting line, what polynomial function should I use? Ex: GetLine([1,2,3,4,5], [1,4,9,16,25], line=True, maxXPower) -> y=x^2
     inverseK: Usually, I'm drawing the curl `y=KX`, while K=[k0,k1,k2,...], X=[x^0,x^1,x^2,...]. If this switch is set, I'll drawing the curl `KY=x`. Don't worry, this switch is transparent to you.
-    Ex: GetMap([0,1,1,4,4,9,9], [0,1,-1,2,-2,3,-3], poly_maxXPower=2, polyLine=True, poly_inverseK=True) -> y^2=x
+    Ex: GetLine([0,1,1,4,4,9,9], [0,1,-1,2,-2,3,-3], poly_maxXPower=2, polyLine=True, poly_inverseK=True) -> y^2=x
     
     ReturnValue:
     void
@@ -40,7 +80,7 @@ def GetMap(arrX, arrY, windowSizeX=12, windowSizeY=8, extendXRate=1, extendYRate
     maxX, maxY = max(arrX) * extendXRate, max(arrY) * extendYRate
     minX, minY = min(arrX) * extendXRate, min(arrY) * extendYRate
     X, Y = numpy.array(arrX), numpy.array(arrY)
-    print('Your input: ', arrX, '|', arrY)
+    print('Your input for {}: {} | {}'.format(name, arrX, arrY))
     if polyLine:
         print('You want a polynomial fitting, with maxXPower = {}, passO = {}, inverseK = {}.'.format(poly_maxXPower, poly_passO, poly_inverseK))
     if smoothLine:
@@ -58,20 +98,16 @@ def GetMap(arrX, arrY, windowSizeX=12, windowSizeY=8, extendXRate=1, extendYRate
 
     # Fire!
     if polyLine:
+        # Generate target line.
         kInit = [1 for _ in range(poly_maxXPower + 1)]
         kInit[0] = 0 # guarantee passO.
         if poly_inverseK:
             kFinal, _ = leastsq(lossFunc, kInit, args=(Y, X))
-            print('Fitting line done. k^-1=', kFinal)
+            print('Fitting polyLine done. k^-1=', kFinal)
         else:
             kFinal, _ = leastsq(lossFunc, kInit, args=(X, Y))
-            print('Fitting line done. k=', kFinal)
-    else:
-        print('Drawing map without fitting a line...\n')
+            print('Fitting polyLine done. k=', kFinal)
 
-    # Draw function map.
-    plt.figure(figsize=(windowSizeX, windowSizeY))
-    plt.scatter(X, Y, color="red", label="Sample Point", linewidth=3)
     if polyLine:
         if poly_inverseK:
             py = numpy.linspace(minY, maxY, 1000)
@@ -79,20 +115,12 @@ def GetMap(arrX, arrY, windowSizeX=12, windowSizeY=8, extendXRate=1, extendYRate
         else:
             px = numpy.linspace(minX, maxX, 1000)
             py = dotMultiply(kFinal, [px ** power for power in range(poly_maxXPower + 1)])
-        plt.plot(px, py, color="orange", label="Fitting Line", linewidth=2)
-    if smoothLine:
+    elif smoothLine:
         px = numpy.linspace(minX, maxX, 1000)
         py = spline(X, Y, px)
-        plt.plot(px, py, color="orange", label="Fitting Line", linewidth=2)
-    plt.legend()
-    plt.grid()
-    plt.show()
-
-    def toFloat(sstr):
-        if sstr == '':
-            return 0.0
-        else:
-            return float(sstr)
+    else:
+        px, py = [], []
+    return [(name, X, Y, px, py)] #Python list is addible.
 
 def _str_remove_extra_space(s):
     begin, end = 0, len(s)
-- 
GitLab