from spyre import server

class SimpleApp(server.App):
    pass

app = SimpleApp()
# app.launch()  # this won't launch from ipython notebook, uncomment to actually launch

"""
Explanation: Simple Interactive Web Applications with Spyre
Spyre is a web application framework for turning static data tables and plots into interactive web apps.  Spyre was motivated by <a href="http://shiny.rstudio.com/">Shiny</a>, a similar framework for R created by the developers of Rstudio.
Where does Spyre Live?
GitHub: <a href='https://github.com/adamhajari/spyre'>github.com/adamhajari/spyre</a>
Live example of a spyre app: <a href='http://adamhajari.com'>adamhajari.com</a>
Installing Spyre
Spyre depends on:
 - cherrypy (server and backend)
 - jinja2 (html and javascript templating)
 - matplotlib (displaying plots and images)
 - pandas (for working within tabular data)
Assuming you don't have any issues with the above dependencies, you can install spyre via pip:
bash
$ pip install dataspyre
Launching a Spyre App
Spyre's server module has a App class that every Spyre app will needs to inherit. Use the app's launch() method to deploy your app.
End of explanation
"""


inputs = [{ "input_type":"text",
            "variable_name":"freq",
            "label": "frequency",
            "value":5,
            "action_id":"plot_sine_wave"}]

"""
Explanation: If you put the above code in a file called simple_app.py you can launch the app from the command line with
$ python simple_app.py
Make sure you uncomment the last line and don't try to run this within ipython notebook. Things will break.
A Very Simple Example
There are two variables of the App class that need to be overridden to create the UI for a Spyre app: inputs and outputs (a third type, controls, is also often useful, but not absolutely neccessary). All three variables are lists of dictionaries which specify each component's properties. For instance, to create a text box input, overide the App's inputs variable:
End of explanation
"""


outputs = [{"output_type":"plot",
            "output_id":"plot_sine_wave",
            "on_page_load":True }]

"""
Explanation: An input variable's value can be used by any of the app's outputs by referencing the variable_name can. The action_id is a reference to either an output_id of an output element or a control_id of a control element. The output referenced by the above input can be defined by overriding the App's outputs variable:
End of explanation
"""


import numpy as np
from matplotlib import pyplot as plt

def getPlot(self,params):
    f = int(params['freq'])
    x = np.arange(1,6,0.01)
    y = np.sin(f*x)
    plt.plot(x,y)
    return plt.gcf()

"""
Explanation: Notice that the action_id from the text input matches the output_id above. As a result, an update to the text field will trigger an update to the output. Finally, we must override the method that generates the plot. Let's make a sine wave:
End of explanation
"""


from spyre import server

import numpy as np
from matplotlib import pyplot as plt

class SimpleSineApp(server.App):
    title = "Simple App"
    inputs = [{ "input_type":"text",
                "variable_name":"freq",
                "label": "frequency",
                "value":5,
                "action_id":"plot_sine_wave"}]

    outputs = [{"output_type":"plot",
                "output_id":"plot_sine_wave",
                "on_page_load":True }]

    def getPlot(self,params):
        f = float(params['freq'])
        x = np.arange(1,6,0.01)
        y = np.sin(f*x)
        plt.plot(x,y)
        return plt.gcf()

app = SimpleSineApp()
# app.launch()

"""
Explanation: The getPlot method should return a pyplot figure.  
If we put it all together and add a title we get
End of explanation
"""


from googlefinance.client import get_price_data

def getData(params):
        ticker = params['ticker']
        xchng = "NASD"
        param = {
            'q': ticker,  # Stock symbol (ex: "AAPL")
            'i': "86400",  # Interval size in seconds ("86400" = 1 day intervals)
            'x': xchng,  # Stock exchange symbol on which stock is traded (ex: "NASD")
            'p': "3M"  # Period (Ex: "1Y" = 1 year)
        }
        df = get_price_data(param)
        return df.drop('Volume', axis=1)

params = {'ticker':'AAPL'}
df = getData(params)
df.head()

"""
Explanation: Notice that the inputs and outputs variables not only specify what type of elements to include in an app's interface, but also how they're connected to eachother. The connection in this example is simple: a change in the input state updates the output. 
<img src="simple_input-output.png">
Under the Hood: Auto-Generating the Front-End with Jinja
Spyre uses the jinja2 templating library to turn the dictionary inputs and outputs into HTML and javascript. Here's a snippet from Spyre's HTML template that generates the text inputs:
<img src="jinja_snapshot.png">
This (and the rest of the templated page) gets rendered using the app's inputs and outputs attributes
<img src="jinja_rendered.png">
If the above code looks foreign to you or HTML/javascript just isn't your thing, that's fine. No knowledge of anything other than python is required to create and launch a Spyre app.
Another Example
Let's suppose you've written a function to grab historical stock price data from the web. Your function returns a pandas dataframe from which you can easily generate graphs of stock prices over time.
End of explanation
"""


from spyre import server
from googlefinance.client import get_price_data

server.include_df_index = True


class StockExample(server.App):
    title = "Historical Stock Prices"

    inputs = [{
        "type": 'dropdown',
        "label": 'Company',
        "options": [
            {"label": "Google", "value": "GOOG"},
            {"label": "Amazon", "value": "AMZN"},
            {"label": "Apple", "value": "AAPL"}
        ],
        "key": 'ticker',
        "action_id": "plot"
    }]

    outputs = [{
        "type": "plot",
        "id": "plot",
        "control_id": "update_data"
    }]

    def getData(self, params):
        ticker = params['ticker']
        xchng = "NASD"
        param = {
            'q': ticker,  # Stock symbol (ex: "AAPL")
            'i': "86400",  # Interval size in seconds ("86400" = 1 day intervals)
            'x': xchng,  # Stock exchange symbol on which stock is traded (ex: "NASD")
            'p': "3M"  # Period (Ex: "1Y" = 1 year)
        }
        df = get_price_data(param)
        return df.drop('Volume', axis=1)

    def getPlot(self, params):
        df = self.getData(params)
        plt_obj = df.plot()
        plt_obj.set_ylabel("Price")
        plt_obj.set_xlabel("Date")
        plt_obj.set_title(params['ticker'])
        return plt_obj


app = StockExample()
app.launch()

"""
Explanation: To turn this into a Spyre app we just need to put the code that creates the plot into a getPlot() method and define the inputs and outputs. Let's use a dropdown menu input this time.
End of explanation
"""


from spyre import server
from googlefinance.client import get_price_data

server.include_df_index = True


class StockExample(server.App):
    title = "Historical Stock Prices"

    inputs = [{
        "type": 'dropdown',
        "label": 'Company',
        "options": [
            {"label": "Google", "value": "GOOG"},
            {"label": "Amazon", "value": "AMZN"},
            {"label": "Apple", "value": "AAPL"}
        ],
        "key": 'ticker',
    }]

    outputs = [{
        "type": "plot",
        "id": "plot",
        "control_id": "update_data"
    }, {
        "type": "table",
        "id": "table_id",
        "control_id": "update_data"
    }]

    controls = [{
        "type": "button",
        "label": "get stock data",
        "id": "update_data"
    }]

    def getData(self, params):
        ticker = params['ticker']
        xchng = "NASD"
        param = {
            'q': ticker,  # Stock symbol (ex: "AAPL")
            'i': "86400",  # Interval size in seconds ("86400" = 1 day intervals)
            'x': xchng,  # Stock exchange symbol on which stock is traded (ex: "NASD")
            'p': "3M"  # Period (Ex: "1Y" = 1 year)
        }
        df = get_price_data(param)
        return df.drop('Volume', axis=1)

    def getPlot(self, params):
        df = self.getData(params)
        plt_obj = df.plot()
        plt_obj.set_ylabel("Price")
        plt_obj.set_xlabel("Date")
        plt_obj.set_title(params['ticker'])
        return plt_obj


app = StockExample()
app.launch()

"""
Explanation: Let's also add a second output: a data table.  To generate this table we just need to override the getData method (which we've conveniently already done). This method should return a pandas DataFrame.  
Since our inputs can only have one action_id and there are two outputs that need to be updated, we'll also add an update button to our app.  The button is a type of control. All controls have a control_id which can be referenced by our outputs, such that a control action (clicking the button in this case) results in an update to those outputs.
End of explanation
"""


from spyre import server
from googlefinance.client import get_price_data

server.include_df_index = True


class StockExample(server.App):
    title = "Historical Stock Prices"

    inputs = [{
        "type": 'dropdown',
        "label": 'Company',
        "options": [
            {"label": "Google", "value": "GOOG"},
            {"label": "Amazon", "value": "AMZN"},
            {"label": "Apple", "value": "AAPL"}
        ],
        "key": 'ticker',
        "action_id": "update_data"
    }]

    tabs = ["Plot", "Table"]

    outputs = [{
        "type": "plot",
        "id": "plot",
        "control_id": "update_data",
        "tab": "Plot"
    }, {
        "type": "table",
        "id": "table_id",
        "control_id": "update_data",
        "tab": "Table"
    }]

    controls = [{
        "type": "hidden",
        "label": "get stock data",
        "id": "update_data"
    }]

    def getData(self, params):
        ticker = params['ticker']
        xchng = "NASD"
        param = {
            'q': ticker,  # Stock symbol (ex: "AAPL")
            'i': "86400",  # Interval size in seconds ("86400" = 1 day intervals)
            'x': xchng,  # Stock exchange symbol on which stock is traded (ex: "NASD")
            'p': "3M"  # Period (Ex: "1Y" = 1 year)
        }
        df = get_price_data(param)
        return df.drop('Volume', axis=1)

    def getPlot(self, params):
        df = self.getData(params)
        plt_obj = df.plot()
        plt_obj.set_ylabel("Price")
        plt_obj.set_xlabel("Date")
        plt_obj.set_title(params['ticker'])
        return plt_obj


app = StockExample()
app.launch()

"""
Explanation: Finally we'll put each of the outputs in separate tabs and add an action_id to the dropdown input that references the "update_data" control. Now, a change to the input state triggers the button to be "clicked". This makes the existence of a "button" supurfluous, so we'll change the control_type to "hidden"
End of explanation
"""


