2

This class plots a curve. However, the inputs are currently set in main(). I'd like to set them as user-driven from mouse interaction. Some of this is possible and in the Matplotlib docs (see referenced sites below) but it's still not really setting it up to be a 'click and plot'. So, ideally the user would click a button to set the P and then whatever point (on the curve, has to be on the curve) they clicked next would be the new P. Same with Q. I'm sure this is a very simple question for anyone who's used Matplotlib but I'm teaching myself it right now, but it would probably take an entry-level dev just a few minutes to do something that I'm getting nowhere with.

Code:

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid.axislines import SubplotZero
from math import sqrt



class ECC123(object):

    def __init__(self,a,b,px,qx,qy):
        self.a = a
        self.b = b
        self.pxlam = px
        self.qxlam = qx
        self.invertQy = qy
        self.fig = plt.figure(1)
        self.ax = SubplotZero(self.fig, 111)

    def drawAxis(self):
        #fig = plt.figure(1)
        #ax = SubplotZero(fig, 111)
        self.fig.add_subplot(self.ax)
        for direction in ["xzero", "yzero"]:
            self.ax.axis[direction].set_axisline_style("->")
            self.ax.axis[direction].set_visible(True)

    def plotGraph(self):
        self.drawAxis()
        y, x = np.ogrid[-10:10:100j, -10:10:100j]  # range grid  [from : to : how_many_points]
        xlist = x.ravel(); ylist = y.ravel()
        plt.contour(xlist, ylist, self.elliptic_curve(x,y), [0])
        pylam = self.ecclambda(self.pxlam,self.a,self.b)  # calculate P from pxlam
        qylam = self.ecclambda(self.qxlam,self.a,self.b)  # calculate Q from qxlam
        if self.invertQy == 1:  qylam = -qylam # optional, inverts qy to negative on the plot
        plt.plot([self.pxlam,self.qxlam], [pylam,qylam], color = "c", linewidth=1)
        plt.plot([self.pxlam], [pylam], "mo"); plt.plot([self.qxlam], [qylam], "mo")
        plt.text(self.pxlam-0.25,pylam+0.5, '$P$'); plt.text(self.qxlam-0.25,self.qxlam+0.5, '$Q$')
        s = (pylam - qylam)/(self.pxlam - self.qxlam)  # calculate s slope
        xr = s**2 - self.pxlam - self.qxlam  # x-value of R
        yr = pylam + s*(xr - self.pxlam)  # y-value of -R; -y is R (inverted across x-axis)
        plt.plot([xr],[yr],"mo")
        plt.plot([xr],[-yr],"co")
        plt.plot([self.qxlam,xr], [qylam,yr], color = "c", linewidth=1)
        plt.plot([xr,xr], [yr,-yr], "x--")
        plt.text(xr+0.25,yr, '$-R$'); plt.text(xr+0.25,-yr, '$R$')

        plt.grid(True)
        plt.show()

I've been going over the docs in Matplotlib, the scipy cookbook, and related questions here on SO and still not seeing exactly how to do this:

http://matplotlib.org/users/event_handling.html

http://matplotlib.org/1.3.1/api/widgets_api.html#matplotlib.widgets.Button.on_clicked

Cursors for data selection in matplotlib

How can I create a frontend for matplotlib?

http://wiki.scipy.org/Cookbook/Matplotlib

So far, I'm getting little red x's all over when I click and they don't even fall within the curve.

Community
  • 1
  • 1
stackuser
  • 869
  • 16
  • 34
  • I suggest removing all the domain specific stuff. I'm sure the elliptical filters are interesting, but they distract from the question being asked here. Try to get a minimal example. FWIW I'm interested in the answer :) – Cuadue Nov 19 '13 at 23:29

1 Answers1

3

I modified your code a little, so that you can set location of P & Q by left & right click, I didn't accomplish all the graph data updates, the rest is left for you:

from mpl_toolkits.axes_grid.axislines import SubplotZero
import numpy as np
import matplotlib.pyplot as plt
from math import sqrt



class ECC(object):
    """
    class to implement elliptic curve and find P+Q=R on the plot
    """

    def __init__(self,a,b,px,qx,qy):
        """
        initialize input variables
        """
        self.a = a
        self.b = b
        self.pxlam = px
        self.qxlam = qx
        self.invertQy = qy
        self.fig = plt.figure(1)
        self.ax = SubplotZero(self.fig, 111)

    def drawAxis(self):
        """
        draw main x,y axis
        """
        #fig = plt.figure(1)
        #ax = SubplotZero(fig, 111)
        self.fig.add_subplot(self.ax)
        for direction in ["xzero", "yzero"]:
            self.ax.axis[direction].set_axisline_style("->")
            self.ax.axis[direction].set_visible(True)

    def ecclambda(self,xl,a,b):
        """
        returns points elliptic curve for P and Q
        y**2 = x**3 + a*x + b
        """
        return sqrt(xl**3 + a*xl + b)

    def elliptic_curve(self,x,y):
        """
        takes in x,y as set of points, returns the elliptic curve
        y**2 = x**3 + a*x + b
        """
        return pow(y, 2) - pow(x, 3) - x * self.a - self.b

    def onclick(self, event):
        x = event.xdata
        if event.button == 1:
            self.pxlam = x
        if event.button == 3:
            self.qxlam = x

        self.update()

    def update(self):
        pylam = self.ecclambda(self.pxlam,self.a,self.b)  # calculate P from pxlam
        qylam = self.ecclambda(self.qxlam,self.a,self.b)  # calculate Q from qxlam
        self.p.set_data([self.pxlam], [pylam])
        self.q.set_data([self.qxlam], [qylam])
        self.pt.set_x(self.pxlam-0.25)
        self.pt.set_y(pylam+0.5)
        self.qt.set_x(self.qxlam-0.25)
        self.qt.set_y(qylam+0.5)
        plt.gcf().canvas.draw()

    def plotGraph(self):
        """
        main plotting of elliptic curve and points/line for P+Q=R
        P+Q=R --->>>  -R is plotted (xr,yr), R is plotted (xr, -yr)
        conditional with invertQy allows inversion of Q across x-axis; set option in main()
        """
        self.drawAxis()
        y, x = np.ogrid[-10:10:100j, -10:10:100j]  # range grid  [from : to : how_many_points]
        xlist = x.ravel(); ylist = y.ravel()
        plt.contour(xlist, ylist, self.elliptic_curve(x,y), [0])
        pylam = self.ecclambda(self.pxlam,self.a,self.b)  # calculate P from pxlam
        qylam = self.ecclambda(self.qxlam,self.a,self.b)  # calculate Q from qxlam
        if self.invertQy == 1:  qylam = -qylam # optional, inverts qy to negative on the plot
        plt.plot([self.pxlam,self.qxlam], [pylam,qylam], color = "c", linewidth=1)
        self.p = plt.plot([self.pxlam], [pylam], "mo")[0]
        self.q = plt.plot([self.qxlam], [qylam], "mo")[0]
        self.pt = plt.text(self.pxlam-0.25,pylam+0.5, '$P$')
        self.qt = plt.text(self.qxlam-0.25,self.qxlam+0.5, '$Q$')
        s = (pylam - qylam)/(self.pxlam - self.qxlam)  # calculate s slope
        xr = s**2 - self.pxlam - self.qxlam  # x-value of R
        yr = pylam + s*(xr - self.pxlam)  # y-value of -R; -y is R (inverted across x-axis)
        plt.plot([xr],[yr],"mo")
        plt.plot([xr],[-yr],"co")
        plt.plot([self.qxlam,xr], [qylam,yr], color = "c", linewidth=1)
        plt.plot([xr,xr], [yr,-yr], "x--")
        plt.text(xr+0.25,yr, '$-R$'); plt.text(xr+0.25,-yr, '$R$')
        plt.text(-9,6,' P: (%s ,%s) \n Q: (%s ,%s) \n R: (%s ,%s) \n a: %s \n b: %s '
                %(self.pxlam,pylam,self.qxlam,qylam,xr,-yr,self.a,self.b),
                fontsize=10, color = 'blue',bbox=dict(facecolor='tan', alpha=0.5))
        plt.title(r"Elliptic Curve Implementation $y^{2} = x^{3} + a*x + b$", fontsize = 16, color = 'b')
        self.fig.canvas.mpl_connect('button_press_event', self.onclick)
        #[xi,yi] = plt.ginput(0)
        ##print "ginput ",xi,yi
        plt.grid(True)
        plt.show()




def main():

    a = -2; b = 1; px = -1.55; qx = -0.1
    invertQy = 0 # set to 1 if q should be inverted to negative along its y axis
    ec = ECC(a,b,px,qx,invertQy)
    ec.plotGraph()


if __name__ == '__main__':
    main()
HYRY
  • 94,853
  • 25
  • 187
  • 187
  • Wonderful! I was just not seeing exactly where everything would go inside the methods. Thanks +1 and accepted. – stackuser Nov 20 '13 at 21:29