mirror of
https://github.com/wassname/rl-portfolio-management.git
synced 2026-06-27 15:16:39 +08:00
3547 lines
255 KiB
Plaintext
3547 lines
255 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Uses tensorforce tensorforce-0.3.0\n",
|
|
"\n",
|
|
"Took 8 million steps over about a day to warm up. Got 1.6e-4 which is about 25% a month, 25x a year.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.156801Z",
|
|
"start_time": "2018-02-18T06:04:01.702071Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"/home/wassname/.pyenv/versions/3.5.3/envs/jupyter3/lib/python3.5/site-packages/h5py/__init__.py:34: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n",
|
|
" from ._conv import register_converters as _register_converters\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# plotting\n",
|
|
"%matplotlib inline\n",
|
|
"from matplotlib import pyplot as plt\n",
|
|
"import seaborn as sns\n",
|
|
"plt.style.use('ggplot')\n",
|
|
"\n",
|
|
"# numeric\n",
|
|
"import numpy as np\n",
|
|
"from numpy import random\n",
|
|
"import pandas as pd\n",
|
|
"import tensorflow as tf\n",
|
|
"\n",
|
|
"# util\n",
|
|
"from collections import Counter\n",
|
|
"import pdb\n",
|
|
"import glob\n",
|
|
"import time\n",
|
|
"import tempfile\n",
|
|
"import itertools\n",
|
|
"from tqdm import tqdm_notebook as tqdm\n",
|
|
"import datetime\n",
|
|
"\n",
|
|
"# logging\n",
|
|
"import logging\n",
|
|
"logger = log = logging.getLogger(__name__)\n",
|
|
"# log.setLevel(logging.INFO)\n",
|
|
"logging.basicConfig()\n",
|
|
"log.info('%s logger started.', __name__)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.184066Z",
|
|
"start_time": "2018-02-18T06:04:03.160504Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import gym\n",
|
|
"from gym import error, spaces, utils\n",
|
|
"from gym.utils import seeding"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.217260Z",
|
|
"start_time": "2018-02-18T06:04:03.185572Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"os.sys.path.append(os.path.abspath('.'))\n",
|
|
"%reload_ext autoreload\n",
|
|
"%autoreload 2"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.248869Z",
|
|
"start_time": "2018-02-18T06:04:03.218873Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'./outputs/tensorforce-PPO/tensorforce_PPO_crypto-20180218_06-04-03'"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# params\n",
|
|
"window_length = 50\n",
|
|
"cash_bias = 0.0\n",
|
|
"batch_size=128\n",
|
|
"import datetime\n",
|
|
"ts = datetime.datetime.utcnow().strftime('%Y%m%d_%H-%M-%S')\n",
|
|
"save_path = './outputs/tensorforce-PPO/tensorforce_PPO_crypto-%s' % ts\n",
|
|
"# save_path = './outputs/tensorforce-PPO/tensorforce_PPO_crypto_20171105_06-50-31'\n",
|
|
"save_path"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.280876Z",
|
|
"start_time": "2018-02-18T06:04:03.251034Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'logs/tensorforce_PPO_crypto-20180218_06-04-03/run-20180218_06-04-03'"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"log_dir = os.path.join('logs', os.path.splitext(os.path.basename(save_path))[0], 'run-' + ts)\n",
|
|
"try:\n",
|
|
" os.makedirs(log_dir)\n",
|
|
"except OSError:\n",
|
|
" pass\n",
|
|
"log_dir"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Enviroment\n",
|
|
"\n",
|
|
"We will wrap out environment so we can modify the inputs, outputs, and attributes for tensorforce.\n",
|
|
"\n",
|
|
"- tensorforce doesn't like dual outputs so we will concat them\n",
|
|
"- we need to softmax the weights since tensorforce doesn't do this"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.351526Z",
|
|
"start_time": "2018-02-18T06:04:03.282373Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from rl_portfolio_management.environments.portfolio import PortfolioEnv\n",
|
|
"from rl_portfolio_management.wrappers import SoftmaxActions, ConcatStates"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.381275Z",
|
|
"start_time": "2018-02-18T06:04:03.352984Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# tensorforce need us to use this wrapper, passing in a registered gym id\n",
|
|
"# this lets us pass in an object instead of registering it\n",
|
|
"from tensorforce.contrib.openai_gym import OpenAIGym\n",
|
|
"class TFOpenAIGymCust(OpenAIGym):\n",
|
|
" def __init__(self, gym_id, gym):\n",
|
|
" self.gym_id = gym_id\n",
|
|
" self.gym = gym\n",
|
|
" self.visualize = False"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.453822Z",
|
|
"start_time": "2018-02-18T06:04:03.383427Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"[0]"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"df_train = pd.read_hdf('./data/poloniex_30m.hf',key='train')\n",
|
|
"env = PortfolioEnv(\n",
|
|
" df=df_train,\n",
|
|
" steps=40, \n",
|
|
" scale=True, \n",
|
|
" trading_cost=0.0025, \n",
|
|
" window_length = window_length,\n",
|
|
" output_mode='EIIE',\n",
|
|
" random_reset=False\n",
|
|
"# random_reset=False,\n",
|
|
")\n",
|
|
"# wrap it in a few wrappers\n",
|
|
"env = ConcatStates(env)\n",
|
|
"env = SoftmaxActions(env)\n",
|
|
"environment = TFOpenAIGymCust('CryptoPortfolioEIIE-v0', env)\n",
|
|
"\n",
|
|
"env.seed(0)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.480316Z",
|
|
"start_time": "2018-02-18T06:04:03.455462Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"Box(3, 51, 4)"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"environment.gym.observation_space"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.515786Z",
|
|
"start_time": "2018-02-18T06:04:03.483644Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(3, 51, 4)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# sanity check out environment is working\n",
|
|
"state = environment.reset()\n",
|
|
"state, reward, done=environment.execute(env.action_space.sample())\n",
|
|
"state.shape"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:03.547263Z",
|
|
"start_time": "2018-02-18T06:04:03.519542Z"
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(3, 51, 4)"
|
|
]
|
|
},
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# sanity check out environment is working\n",
|
|
"state = env.reset()\n",
|
|
"state, reward, done, info=env.step(env.action_space.sample())\n",
|
|
"state.shape"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2017-07-16T04:41:21.116729Z",
|
|
"start_time": "2017-07-16T12:41:21.086620+08:00"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Model\n",
|
|
"\n",
|
|
"Derived from https://github.com/reinforceio/tensorforce/blob/0d07fadec03f76537a2431e17c51cd759d53b5e9/tensorforce/core/networks/layers.py#L90\n",
|
|
"\n",
|
|
"Implemented as per CNN version of https://arxiv.org/abs/1706.10059 "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:04.051084Z",
|
|
"start_time": "2018-02-18T06:04:03.548936Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# from tensorforce import Configuration\n",
|
|
"from tensorforce.agents import PPOAgent\n",
|
|
"from tensorforce.core.networks import LayeredNetwork, layers, Network, network"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2017-11-05T08:59:45.251938Z",
|
|
"start_time": "2017-11-05T08:59:45.184678Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:04.490820Z",
|
|
"start_time": "2018-02-18T06:04:04.052560Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from tensorforce.core.networks import Layer, Conv2d, Nonlinearity\n",
|
|
"class EIIE(Layer):\n",
|
|
" \"\"\"\n",
|
|
" EIIE layer\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" def __init__(self,\n",
|
|
" size=20,\n",
|
|
" bias=True,\n",
|
|
" activation='relu',\n",
|
|
" l2_regularization=0.0,\n",
|
|
" l1_regularization=0.0,\n",
|
|
" scope='EIIE',\n",
|
|
" summary_labels=()):\n",
|
|
" self.size = size\n",
|
|
" # Expectation is broadcast back over advantage values so output is of size 1\n",
|
|
" self.conv1 = Conv2d(\n",
|
|
" size=3,\n",
|
|
" bias=bias,\n",
|
|
" stride=(1,1),\n",
|
|
" window=(1,3),\n",
|
|
" padding='VALID',\n",
|
|
" l2_regularization=l2_regularization,\n",
|
|
" l1_regularization=l1_regularization,\n",
|
|
" summary_labels=summary_labels)\n",
|
|
" self.conv2 = Conv2d(\n",
|
|
" size=size,\n",
|
|
" bias=bias,\n",
|
|
" stride=(1,window_length-2-1),\n",
|
|
" window=(1,window_length-2-1),\n",
|
|
" padding='VALID',\n",
|
|
" l2_regularization=l2_regularization,\n",
|
|
" l1_regularization=l1_regularization,\n",
|
|
" summary_labels=summary_labels)\n",
|
|
" self.conv3 = Conv2d(\n",
|
|
" size=1,\n",
|
|
" bias=bias,\n",
|
|
" stride=(1,1),\n",
|
|
" window=(1,1),\n",
|
|
" l2_regularization=l2_regularization,\n",
|
|
" l1_regularization=l1_regularization,\n",
|
|
" summary_labels=summary_labels)\n",
|
|
" self.nonlinearity = Nonlinearity(\n",
|
|
" name=activation, summary_labels=summary_labels)\n",
|
|
" self.nonlinearity2 = Nonlinearity(\n",
|
|
" name=activation, summary_labels=summary_labels)\n",
|
|
" super(EIIE, self).__init__(\n",
|
|
" scope=scope, summary_labels=summary_labels)\n",
|
|
"\n",
|
|
" def tf_apply(self, x0, update):\n",
|
|
" # where window_size=50, actions=4 (giving the 3), data cols=5\n",
|
|
" # x0 = (None,3,50,5)\n",
|
|
" # x = (None,3,49,5)\n",
|
|
" # x = (None,3,1,1)\n",
|
|
" # conv1 => (None,3, 47,3)\n",
|
|
" # conv2 => (None,3, 1, 20)\n",
|
|
" # concat=> (None,3, 1, 21)\n",
|
|
" # conv3 => (None,3, 1, 1)\n",
|
|
" # concat=> (None,2, 1, 1)\n",
|
|
"\n",
|
|
" w0 = x0[:,:,:1,:1]\n",
|
|
" x = x0[:,:,1:,:]\n",
|
|
" \n",
|
|
" x = self.conv1.apply(x, update=update)\n",
|
|
" x = self.nonlinearity.apply(x=x, update=update)\n",
|
|
" \n",
|
|
" x = self.conv2.apply(x, update=update)\n",
|
|
" x = self.nonlinearity2.apply(x=x, update=update)\n",
|
|
" \n",
|
|
" x = tf.concat([x, w0], 3)\n",
|
|
" x = self.conv3.apply(x, update=update)\n",
|
|
" \n",
|
|
" # concat on cash_bias\n",
|
|
" cash_bias_int = 0\n",
|
|
" # FIXME not sure how to make shape with a flexible size in tensorflow but this works for now\n",
|
|
" # cash_bias = tf.ones(shape=(batch_size,1,1,1)) * cash_bias_int\n",
|
|
" cash_bias = x[:,:1,:1,:1]*0 \n",
|
|
" x = tf.concat([cash_bias, x], 1)\n",
|
|
"\n",
|
|
" if 'activations' in self.summary_labels:\n",
|
|
" summary = tf.summary.histogram(name='activations', values=x)\n",
|
|
" self.summaries.append(summary)\n",
|
|
"\n",
|
|
" return x\n",
|
|
"\n",
|
|
" def tf_regularization_loss(self):\n",
|
|
" if super(EIIE, self).tf_regularization_loss() is None:\n",
|
|
" losses = list()\n",
|
|
" else:\n",
|
|
" losses = [super(EIIE, self).tf_regularization_loss()]\n",
|
|
"\n",
|
|
" if self.conv1.regularization_loss() is not None:\n",
|
|
" losses.append(self.conv1.regularization_loss())\n",
|
|
" if self.conv2.regularization_loss() is not None:\n",
|
|
" losses.append(self.conv2.regularization_loss())\n",
|
|
" if self.conv1.regularization_loss() is not None:\n",
|
|
" losses.append(self.conv3.regularization_loss())\n",
|
|
"\n",
|
|
" if len(losses) > 0:\n",
|
|
" return tf.add_n(inputs=losses)\n",
|
|
" else:\n",
|
|
" return None\n",
|
|
"\n",
|
|
" def get_variables(self, include_non_trainable=False):\n",
|
|
" layer_variables = super(EIIE, self).get_variables(\n",
|
|
" include_non_trainable=include_non_trainable)\n",
|
|
"\n",
|
|
" layer_variables += self.conv1.get_variables(\n",
|
|
" include_non_trainable=include_non_trainable)\n",
|
|
" layer_variables += self.conv2.get_variables(\n",
|
|
" include_non_trainable=include_non_trainable)\n",
|
|
" layer_variables += self.conv3.get_variables(\n",
|
|
" include_non_trainable=include_non_trainable)\n",
|
|
"\n",
|
|
" layer_variables += self.nonlinearity.get_variables(\n",
|
|
" include_non_trainable=include_non_trainable)\n",
|
|
" layer_variables += self.nonlinearity.get_variables(\n",
|
|
" include_non_trainable=include_non_trainable)\n",
|
|
"\n",
|
|
" return layer_variables\n",
|
|
" \n",
|
|
"# Add our custom layer\n",
|
|
"layers['EIIE'] = EIIE\n",
|
|
"\n",
|
|
"# Network as list of layers\n",
|
|
"network_spec = [\n",
|
|
" dict(type='EIIE', \n",
|
|
" l1_regularization=1e-8,\n",
|
|
" l2_regularization=1e-8),\n",
|
|
" dict(type='flatten')\n",
|
|
"]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:04.546172Z",
|
|
"start_time": "2018-02-18T06:04:04.500730Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# also add a custom baseline\n",
|
|
"from tensorforce.core.baselines import NetworkBaseline\n",
|
|
"from tensorforce.core.baselines import baselines\n",
|
|
"\n",
|
|
"\n",
|
|
"class EIIEBaseline(NetworkBaseline):\n",
|
|
" \"\"\"\n",
|
|
" CNN baseline (single-state) consisting of convolutional layers followed by dense layers.\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" def __init__(self, layers_spec, scope='eiie-baseline', summary_labels=()):\n",
|
|
" \"\"\"\n",
|
|
" CNN baseline.\n",
|
|
" Args:\n",
|
|
" conv_sizes: List of convolutional layer sizes\n",
|
|
" dense_sizes: List of dense layer sizes\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" super(EIIEBaseline, self).__init__(layers_spec, scope, summary_labels)\n",
|
|
" \n",
|
|
"# Add our custom baseline\n",
|
|
"baselines['EIIE']=EIIEBaseline"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Agent\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:20.033172Z",
|
|
"start_time": "2018-02-18T06:04:04.547697Z"
|
|
},
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"INFO:tensorflow:Create CheckpointSaverHook.\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"INFO:tensorflow:Create CheckpointSaverHook.\n",
|
|
"[2018-02-18 14:04:13,676] Create CheckpointSaverHook.\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"INFO:tensorflow:Saving checkpoints for 0 into ./outputs/tensorforce-PPO/tensorforce_PPO_crypto-20180218_06-04-03/model.ckpt.\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"INFO:tensorflow:Saving checkpoints for 0 into ./outputs/tensorforce-PPO/tensorforce_PPO_crypto-20180218_06-04-03/model.ckpt.\n",
|
|
"[2018-02-18 14:04:18,901] Saving checkpoints for 0 into ./outputs/tensorforce-PPO/tensorforce_PPO_crypto-20180218_06-04-03/model.ckpt.\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"explorations_spec=dict(\n",
|
|
" type=\"epsilon_anneal\",\n",
|
|
" initial_epsilon=1.0,\n",
|
|
" final_epsilon= 0.005,\n",
|
|
" timesteps= int(5e3),\n",
|
|
" start_timestep=0,\n",
|
|
")\n",
|
|
"\n",
|
|
"# I want to use a gaussian dist instead of beta, we will apply post processing to scale everything\n",
|
|
"# actions_spec = environment.actions.copy()\n",
|
|
"# del actions_spec[\"min_value\"]\n",
|
|
"# del actions_spec[\"max_value\"]\n",
|
|
"# distributions_spec=dict(action=dict(type='gaussian', mean=0.25, log_stddev=np.log(5e-2)))\n",
|
|
"\n",
|
|
"# Or just use beta:\n",
|
|
"actions_spec = environment.actions.copy()\n",
|
|
"distributions_spec=None\n",
|
|
"\n",
|
|
"# https://github.com/reinforceio/tensorforce/blob/d823809df746c61471e2cba5832ab051581baf7e/docs/summary_spec.md\n",
|
|
"summary_spec = dict(directory=log_dir, \n",
|
|
" steps=50,\n",
|
|
" labels=[\n",
|
|
" 'configuration',\n",
|
|
"# 'gradients_scalar',\n",
|
|
"# 'regularization',\n",
|
|
" 'inputs',\n",
|
|
" 'losses',\n",
|
|
"# 'variables'\n",
|
|
" 'total-loss',\n",
|
|
" ]\n",
|
|
" )\n",
|
|
"agent = PPOAgent(\n",
|
|
" states_spec=environment.states,\n",
|
|
" actions_spec=actions_spec,\n",
|
|
" network_spec=network_spec,\n",
|
|
" batch_size=4096,\n",
|
|
" saver_spec = dict(\n",
|
|
" directory=save_path, \n",
|
|
" steps=2000000, \n",
|
|
"# basename=os.path.basename(save_path)\n",
|
|
" ),\n",
|
|
" # Agent\n",
|
|
" states_preprocessing_spec=None,\n",
|
|
" explorations_spec=explorations_spec,\n",
|
|
" reward_preprocessing_spec=None,\n",
|
|
" # BatchAgent\n",
|
|
" keep_last_timestep=True,\n",
|
|
" # PPOAgent\n",
|
|
" step_optimizer=dict(\n",
|
|
" type='adam',\n",
|
|
" learning_rate=1e-4\n",
|
|
" ),\n",
|
|
" optimization_steps=10,\n",
|
|
" # Model\n",
|
|
" scope='ppo',\n",
|
|
" discount=0.0,\n",
|
|
" # DistributionModel\n",
|
|
" distributions_spec=distributions_spec,\n",
|
|
" entropy_regularization=0.00, # 0 and 0.01 in baselines\n",
|
|
" # PGModel\n",
|
|
" baseline_mode='states',\n",
|
|
" baseline=dict(\n",
|
|
" type=\"EIIE\",\n",
|
|
" layers_spec=network_spec\n",
|
|
"# update_batch_size=512,\n",
|
|
" ), # string indicating the baseline value function (currently 'linear' or 'mlp').\n",
|
|
" baseline_optimizer=dict(type='adam', learning_rate=3e-4),\n",
|
|
" gae_lambda=0.97,\n",
|
|
" # PGLRModel\n",
|
|
" likelihood_ratio_clipping=0.2,\n",
|
|
" summary_spec=summary_spec,\n",
|
|
" distributed_spec=None\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-01-23T04:19:14.046363Z",
|
|
"start_time": "2018-01-23T04:18:45.267535Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Train\n",
|
|
"\n",
|
|
"## Callbacks"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:04:20.068676Z",
|
|
"start_time": "2018-02-18T06:04:20.034834Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from rl_portfolio_management.callbacks.tensorforce import EpisodeFinishedTQDM, EpisodeFinished\n",
|
|
"from rl_portfolio_management.util import MDD, sharpe"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2017-10-15T02:19:28.278977Z",
|
|
"start_time": "2017-10-15T02:19:28.132177Z"
|
|
}
|
|
},
|
|
"source": [
|
|
"## Train"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.502Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"from tensorforce.execution import Runner\n",
|
|
"runner = Runner(agent=agent, environment=environment)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.513Z"
|
|
},
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "3c94d62b1fed4d698155ee9f1145590f",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/html": [
|
|
"<p>Failed to display Jupyter Widget of type <code>HBox</code>.</p>\n",
|
|
"<p>\n",
|
|
" If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean\n",
|
|
" that the widgets JavaScript is still loading. If this message persists, it\n",
|
|
" likely means that the widgets JavaScript library is either not installed or\n",
|
|
" not enabled. See the <a href=\"https://ipywidgets.readthedocs.io/en/stable/user_install.html\">Jupyter\n",
|
|
" Widgets Documentation</a> for setup instructions.\n",
|
|
"</p>\n",
|
|
"<p>\n",
|
|
" If you're reading this message in another frontend (for example, a static\n",
|
|
" rendering on GitHub or <a href=\"https://nbviewer.jupyter.org/\">NBViewer</a>),\n",
|
|
" it may mean that your frontend doesn't currently support widgets.\n",
|
|
"</p>\n"
|
|
],
|
|
"text/plain": [
|
|
"HBox(children=(IntProgress(value=0, max=12000000), HTML(value='')))"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"TensorBoardLogger started. Run `tensorboard --logdir=/media/oldhome/wassname/Documents/projects/rl-portfolio-gh/rl-portfolio-management-gh/logs/tensorforce_PPO_crypto-20180218_06-04-03` to visualize\n",
|
|
"ep reward: -0.00001528 [-0.00010269, 0.00005232], portfolio_value: 0.9753 mdd=0.58% sharpe=-2.0429, expl= 0.00% eps=1000 weights={'XMRBTC': 0.21799999475479126, 'DASHBTC': 0.24740000069141388, 'LTCBTC': 0.2702000141143799}\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.get_websocket_type = function() {\n",
|
|
" if (typeof(WebSocket) !== 'undefined') {\n",
|
|
" return WebSocket;\n",
|
|
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
|
|
" return MozWebSocket;\n",
|
|
" } else {\n",
|
|
" alert('Your browser does not have WebSocket support.' +\n",
|
|
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
|
|
" 'Firefox 4 and 5 are also supported but you ' +\n",
|
|
" 'have to enable WebSockets in about:config.');\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
|
|
" this.id = figure_id;\n",
|
|
"\n",
|
|
" this.ws = websocket;\n",
|
|
"\n",
|
|
" this.supports_binary = (this.ws.binaryType != undefined);\n",
|
|
"\n",
|
|
" if (!this.supports_binary) {\n",
|
|
" var warnings = document.getElementById(\"mpl-warnings\");\n",
|
|
" if (warnings) {\n",
|
|
" warnings.style.display = 'block';\n",
|
|
" warnings.textContent = (\n",
|
|
" \"This browser does not support binary websocket messages. \" +\n",
|
|
" \"Performance may be slow.\");\n",
|
|
" }\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj = new Image();\n",
|
|
"\n",
|
|
" this.context = undefined;\n",
|
|
" this.message = undefined;\n",
|
|
" this.canvas = undefined;\n",
|
|
" this.rubberband_canvas = undefined;\n",
|
|
" this.rubberband_context = undefined;\n",
|
|
" this.format_dropdown = undefined;\n",
|
|
"\n",
|
|
" this.image_mode = 'full';\n",
|
|
"\n",
|
|
" this.root = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(this.root);\n",
|
|
"\n",
|
|
" this._init_header(this);\n",
|
|
" this._init_canvas(this);\n",
|
|
" this._init_toolbar(this);\n",
|
|
"\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" this.waiting = false;\n",
|
|
"\n",
|
|
" this.ws.onopen = function () {\n",
|
|
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
|
|
" fig.send_message(\"send_image_mode\", {});\n",
|
|
" if (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
|
|
" return false;\n",
|
|
" });\n",
|
|
"\n",
|
|
" function set_focus () {\n",
|
|
" canvas.focus();\n",
|
|
" canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" window.setTimeout(set_focus, 100);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>')\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" for(var toolbar_ind in mpl.toolbar_items) {\n",
|
|
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
|
|
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
|
|
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
|
|
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
|
|
"\n",
|
|
" if (!name) {\n",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option)\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
|
|
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
|
|
" // which will in turn request a refresh of the image.\n",
|
|
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.send_message = function(type, properties) {\n",
|
|
" properties['type'] = type;\n",
|
|
" properties['figure_id'] = this.id;\n",
|
|
" this.ws.send(JSON.stringify(properties));\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.send_draw_message = function() {\n",
|
|
" if (!this.waiting) {\n",
|
|
" this.waiting = true;\n",
|
|
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
|
|
" var format_dropdown = fig.format_dropdown;\n",
|
|
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
|
|
" fig.ondownload(fig, format);\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
|
|
" var size = msg['size'];\n",
|
|
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
|
|
" fig._resize_canvas(size[0], size[1]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
|
|
" x0 = Math.floor(x0) + 0.5;\n",
|
|
" y0 = Math.floor(y0) + 0.5;\n",
|
|
" x1 = Math.floor(x1) + 0.5;\n",
|
|
" y1 = Math.floor(y1) + 0.5;\n",
|
|
" var min_x = Math.min(x0, x1);\n",
|
|
" var min_y = Math.min(y0, y1);\n",
|
|
" var width = Math.abs(x1 - x0);\n",
|
|
" var height = Math.abs(y1 - y0);\n",
|
|
"\n",
|
|
" fig.rubberband_context.clearRect(\n",
|
|
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
|
|
" fig.imageObj.src = evt.data;\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var msg = JSON.parse(evt.data);\n",
|
|
" var msg_type = msg['type'];\n",
|
|
"\n",
|
|
" // Call the \"handle_{type}\" callback, which takes\n",
|
|
" // the figure and JSON message as its only arguments.\n",
|
|
" try {\n",
|
|
" var callback = fig[\"handle_\" + msg_type];\n",
|
|
" } catch (e) {\n",
|
|
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" if (callback) {\n",
|
|
" try {\n",
|
|
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
|
|
" callback(fig, msg);\n",
|
|
" } catch (e) {\n",
|
|
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" return obj;\n",
|
|
" }, {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
|
|
" var canvas_pos = mpl.findpos(event)\n",
|
|
"\n",
|
|
" if (name === 'button_press')\n",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\n",
|
|
"\n",
|
|
" /* This prevents the web browser from automatically changing to\n",
|
|
" * the text insertion cursor when the button is pressed. We want\n",
|
|
" * to control all of the cursor setting manually through the\n",
|
|
" * 'cursor' event from matplotlib */\n",
|
|
" event.preventDefault();\n",
|
|
" return false;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" // Handle any extra behaviour associated with a key event\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.key_event = function(event, name) {\n",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" guiEvent: simpleKeys(event)});\n",
|
|
" return false;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
|
|
" if (name == 'download') {\n",
|
|
" this.handle_save(this, null);\n",
|
|
" } else {\n",
|
|
" this.send_message(\"toolbar_button\", {name: name});\n",
|
|
" }\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
|
|
" this.message.textContent = tooltip;\n",
|
|
"};\n",
|
|
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overriden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\n",
|
|
"\n",
|
|
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
|
|
" // web socket which is closed, not our websocket->open comm proxy.\n",
|
|
" ws_proxy.onopen();\n",
|
|
"\n",
|
|
" fig.parent_element = element.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\n",
|
|
"\n",
|
|
" // Update the output cell to use the data from the current canvas.\n",
|
|
" fig.push_to_output();\n",
|
|
" var dataURL = fig.canvas.toDataURL();\n",
|
|
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
|
|
" // the notebook keyboard shortcuts fail.\n",
|
|
" IPython.keyboard_manager.enable()\n",
|
|
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>')\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" for(var toolbar_ind in mpl.toolbar_items){\n",
|
|
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
|
|
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
|
|
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
|
|
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
|
|
"\n",
|
|
" if (!name) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('tabindex', 0)\n",
|
|
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
|
|
" // off when our div gets focus\n",
|
|
"\n",
|
|
" // location in version 3\n",
|
|
" if (IPython.notebook.keyboard_manager) {\n",
|
|
" IPython.notebook.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // Check for shift+enter\n",
|
|
" if (event.shiftKey && event.which == 13) {\n",
|
|
" this.canvas_div.blur();\n",
|
|
" event.shiftKey = false;\n",
|
|
" // Send a \"J\" for go to next cell\n",
|
|
" event.which = 74;\n",
|
|
" event.keyCode = 74;\n",
|
|
" manager.command_mode();\n",
|
|
" manager.handle_keydown(event);\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
|
|
" fig.ondownload(fig, null);\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.find_output_cell = function(html_output) {\n",
|
|
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
|
|
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
|
|
" // IPython event is triggered only after the cells have been serialised, which for\n",
|
|
" // our purposes (turning an active figure into a static one), is too late.\n",
|
|
" var cells = IPython.notebook.get_cells();\n",
|
|
" var ncells = cells.length;\n",
|
|
" for (var i=0; i<ncells; i++) {\n",
|
|
" var cell = cells[i];\n",
|
|
" if (cell.cell_type === 'code'){\n",
|
|
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
|
|
" var data = cell.output_area.outputs[j];\n",
|
|
" if (data.data) {\n",
|
|
" // IPython >= 3 moved mimebundle to data attribute of output\n",
|
|
" data = data.data;\n",
|
|
" }\n",
|
|
" if (data['text/html'] == html_output) {\n",
|
|
" return [cell, data, j];\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"// Register the function which deals with the matplotlib target/channel.\n",
|
|
"// The kernel may be null if the page has been refreshed.\n",
|
|
"if (IPython.notebook.kernel != null) {\n",
|
|
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,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kBPQWiPK0UNdRraI3/s7hL+z1iLM40JjzK70yPbVlpaWWkQj0lr/zlq71HVdbzdyLBZbZK39M2PMrJCbzeqRAAmEgADFNgSNwCqUhkB6zXariNw9dOjQ+/bu3XutUmoZ1mxFBOu54wcPHvxfDh48OEhEsInqCxli26iUejsej/8tanvdddcNqa+vf1NE/m7o0KEI7yj79u3D1HPbsmXLNpbGIpZCAiRQKQQotpXSUqxnUQgsWLBgaiQS+WcROQu7kZGpUmpLNBq9L5lMItD8VMQptdberZT6SYbYXiQivxCRBqXUr+Lx+Ne11mNF5Aci8lkRiYjIayKCGLmIG8xEAiRAAscIUGzZGUiABEiABEggYAIU24ABM3sSIAESIAESoNiyD5AACZAACZBAwAQotgEDZvYkQAIkQAIkQLFlHyABEiABEiCBgAlQbAMGzOxJgARIgARIgGLLPkACJEACJEACAROg2AYMmNmTAAmQAAmQAMWWfYAESIAESIAEAiZAsQ0YMLMnARIgARIgAYot+wAJkAAJkAAJBEyAYhswYGZPAiRAAiRAAhRb9gESIAESIAESCJgAxTZgwMyeBEiABEiABCi27AMkQAIkQAIkEDABim3AgJk9CZAACZAACVBs2QdIgARIgARIIGACFNuAATN7EiABEiABEqDYsg+QAAmQAAmQQMAEKLYBA2b2JEACJEACJECxZR8gARIgARIggYAJUGwDBszsSYAESIAESIBiyz5AAiRAAiRAAgEToNgGDJjZkwAJkAAJkADFln2ABEiABEiABAImQLENGDCzJwESIAESIAGKLfsACZAACZAACQRMgGIbMGBmTwIkQAIkQAIUW/YBEiABEiABEgiYAMU2YMDMngRIgARIgAQotuwDJEACJEACJBAwAYptwICZPQmQAAmQAAlQbNkHSIAESIAESCBgAv8fZezeSGnUI4EAAAAASUVORK5CYII=\" width=\"431.818172458775\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.get_websocket_type = function() {\n",
|
|
" if (typeof(WebSocket) !== 'undefined') {\n",
|
|
" return WebSocket;\n",
|
|
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
|
|
" return MozWebSocket;\n",
|
|
" } else {\n",
|
|
" alert('Your browser does not have WebSocket support.' +\n",
|
|
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
|
|
" 'Firefox 4 and 5 are also supported but you ' +\n",
|
|
" 'have to enable WebSockets in about:config.');\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
|
|
" this.id = figure_id;\n",
|
|
"\n",
|
|
" this.ws = websocket;\n",
|
|
"\n",
|
|
" this.supports_binary = (this.ws.binaryType != undefined);\n",
|
|
"\n",
|
|
" if (!this.supports_binary) {\n",
|
|
" var warnings = document.getElementById(\"mpl-warnings\");\n",
|
|
" if (warnings) {\n",
|
|
" warnings.style.display = 'block';\n",
|
|
" warnings.textContent = (\n",
|
|
" \"This browser does not support binary websocket messages. \" +\n",
|
|
" \"Performance may be slow.\");\n",
|
|
" }\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj = new Image();\n",
|
|
"\n",
|
|
" this.context = undefined;\n",
|
|
" this.message = undefined;\n",
|
|
" this.canvas = undefined;\n",
|
|
" this.rubberband_canvas = undefined;\n",
|
|
" this.rubberband_context = undefined;\n",
|
|
" this.format_dropdown = undefined;\n",
|
|
"\n",
|
|
" this.image_mode = 'full';\n",
|
|
"\n",
|
|
" this.root = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(this.root);\n",
|
|
"\n",
|
|
" this._init_header(this);\n",
|
|
" this._init_canvas(this);\n",
|
|
" this._init_toolbar(this);\n",
|
|
"\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" this.waiting = false;\n",
|
|
"\n",
|
|
" this.ws.onopen = function () {\n",
|
|
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
|
|
" fig.send_message(\"send_image_mode\", {});\n",
|
|
" if (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
|
|
" return false;\n",
|
|
" });\n",
|
|
"\n",
|
|
" function set_focus () {\n",
|
|
" canvas.focus();\n",
|
|
" canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" window.setTimeout(set_focus, 100);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>')\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" for(var toolbar_ind in mpl.toolbar_items) {\n",
|
|
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
|
|
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
|
|
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
|
|
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
|
|
"\n",
|
|
" if (!name) {\n",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option)\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
|
|
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
|
|
" // which will in turn request a refresh of the image.\n",
|
|
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.send_message = function(type, properties) {\n",
|
|
" properties['type'] = type;\n",
|
|
" properties['figure_id'] = this.id;\n",
|
|
" this.ws.send(JSON.stringify(properties));\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.send_draw_message = function() {\n",
|
|
" if (!this.waiting) {\n",
|
|
" this.waiting = true;\n",
|
|
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
|
|
" var format_dropdown = fig.format_dropdown;\n",
|
|
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
|
|
" fig.ondownload(fig, format);\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
|
|
" var size = msg['size'];\n",
|
|
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
|
|
" fig._resize_canvas(size[0], size[1]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
|
|
" x0 = Math.floor(x0) + 0.5;\n",
|
|
" y0 = Math.floor(y0) + 0.5;\n",
|
|
" x1 = Math.floor(x1) + 0.5;\n",
|
|
" y1 = Math.floor(y1) + 0.5;\n",
|
|
" var min_x = Math.min(x0, x1);\n",
|
|
" var min_y = Math.min(y0, y1);\n",
|
|
" var width = Math.abs(x1 - x0);\n",
|
|
" var height = Math.abs(y1 - y0);\n",
|
|
"\n",
|
|
" fig.rubberband_context.clearRect(\n",
|
|
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
|
|
" fig.imageObj.src = evt.data;\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var msg = JSON.parse(evt.data);\n",
|
|
" var msg_type = msg['type'];\n",
|
|
"\n",
|
|
" // Call the \"handle_{type}\" callback, which takes\n",
|
|
" // the figure and JSON message as its only arguments.\n",
|
|
" try {\n",
|
|
" var callback = fig[\"handle_\" + msg_type];\n",
|
|
" } catch (e) {\n",
|
|
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" if (callback) {\n",
|
|
" try {\n",
|
|
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
|
|
" callback(fig, msg);\n",
|
|
" } catch (e) {\n",
|
|
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" return obj;\n",
|
|
" }, {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
|
|
" var canvas_pos = mpl.findpos(event)\n",
|
|
"\n",
|
|
" if (name === 'button_press')\n",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\n",
|
|
"\n",
|
|
" /* This prevents the web browser from automatically changing to\n",
|
|
" * the text insertion cursor when the button is pressed. We want\n",
|
|
" * to control all of the cursor setting manually through the\n",
|
|
" * 'cursor' event from matplotlib */\n",
|
|
" event.preventDefault();\n",
|
|
" return false;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" // Handle any extra behaviour associated with a key event\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.key_event = function(event, name) {\n",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" guiEvent: simpleKeys(event)});\n",
|
|
" return false;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
|
|
" if (name == 'download') {\n",
|
|
" this.handle_save(this, null);\n",
|
|
" } else {\n",
|
|
" this.send_message(\"toolbar_button\", {name: name});\n",
|
|
" }\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
|
|
" this.message.textContent = tooltip;\n",
|
|
"};\n",
|
|
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overriden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\n",
|
|
"\n",
|
|
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
|
|
" // web socket which is closed, not our websocket->open comm proxy.\n",
|
|
" ws_proxy.onopen();\n",
|
|
"\n",
|
|
" fig.parent_element = element.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\n",
|
|
"\n",
|
|
" // Update the output cell to use the data from the current canvas.\n",
|
|
" fig.push_to_output();\n",
|
|
" var dataURL = fig.canvas.toDataURL();\n",
|
|
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
|
|
" // the notebook keyboard shortcuts fail.\n",
|
|
" IPython.keyboard_manager.enable()\n",
|
|
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>')\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" for(var toolbar_ind in mpl.toolbar_items){\n",
|
|
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
|
|
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
|
|
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
|
|
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
|
|
"\n",
|
|
" if (!name) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('tabindex', 0)\n",
|
|
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
|
|
" // off when our div gets focus\n",
|
|
"\n",
|
|
" // location in version 3\n",
|
|
" if (IPython.notebook.keyboard_manager) {\n",
|
|
" IPython.notebook.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // Check for shift+enter\n",
|
|
" if (event.shiftKey && event.which == 13) {\n",
|
|
" this.canvas_div.blur();\n",
|
|
" event.shiftKey = false;\n",
|
|
" // Send a \"J\" for go to next cell\n",
|
|
" event.which = 74;\n",
|
|
" event.keyCode = 74;\n",
|
|
" manager.command_mode();\n",
|
|
" manager.handle_keydown(event);\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
|
|
" fig.ondownload(fig, null);\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.find_output_cell = function(html_output) {\n",
|
|
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
|
|
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
|
|
" // IPython event is triggered only after the cells have been serialised, which for\n",
|
|
" // our purposes (turning an active figure into a static one), is too late.\n",
|
|
" var cells = IPython.notebook.get_cells();\n",
|
|
" var ncells = cells.length;\n",
|
|
" for (var i=0; i<ncells; i++) {\n",
|
|
" var cell = cells[i];\n",
|
|
" if (cell.cell_type === 'code'){\n",
|
|
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
|
|
" var data = cell.output_area.outputs[j];\n",
|
|
" if (data.data) {\n",
|
|
" // IPython >= 3 moved mimebundle to data attribute of output\n",
|
|
" data = data.data;\n",
|
|
" }\n",
|
|
" if (data['text/html'] == html_output) {\n",
|
|
" return [cell, data, j];\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"// Register the function which deals with the matplotlib target/channel.\n",
|
|
"// The kernel may be null if the page has been refreshed.\n",
|
|
"if (IPython.notebook.kernel != null) {\n",
|
|
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,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\" width=\"431.818172458775\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.get_websocket_type = function() {\n",
|
|
" if (typeof(WebSocket) !== 'undefined') {\n",
|
|
" return WebSocket;\n",
|
|
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
|
|
" return MozWebSocket;\n",
|
|
" } else {\n",
|
|
" alert('Your browser does not have WebSocket support.' +\n",
|
|
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
|
|
" 'Firefox 4 and 5 are also supported but you ' +\n",
|
|
" 'have to enable WebSockets in about:config.');\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
|
|
" this.id = figure_id;\n",
|
|
"\n",
|
|
" this.ws = websocket;\n",
|
|
"\n",
|
|
" this.supports_binary = (this.ws.binaryType != undefined);\n",
|
|
"\n",
|
|
" if (!this.supports_binary) {\n",
|
|
" var warnings = document.getElementById(\"mpl-warnings\");\n",
|
|
" if (warnings) {\n",
|
|
" warnings.style.display = 'block';\n",
|
|
" warnings.textContent = (\n",
|
|
" \"This browser does not support binary websocket messages. \" +\n",
|
|
" \"Performance may be slow.\");\n",
|
|
" }\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj = new Image();\n",
|
|
"\n",
|
|
" this.context = undefined;\n",
|
|
" this.message = undefined;\n",
|
|
" this.canvas = undefined;\n",
|
|
" this.rubberband_canvas = undefined;\n",
|
|
" this.rubberband_context = undefined;\n",
|
|
" this.format_dropdown = undefined;\n",
|
|
"\n",
|
|
" this.image_mode = 'full';\n",
|
|
"\n",
|
|
" this.root = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(this.root);\n",
|
|
"\n",
|
|
" this._init_header(this);\n",
|
|
" this._init_canvas(this);\n",
|
|
" this._init_toolbar(this);\n",
|
|
"\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" this.waiting = false;\n",
|
|
"\n",
|
|
" this.ws.onopen = function () {\n",
|
|
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
|
|
" fig.send_message(\"send_image_mode\", {});\n",
|
|
" if (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
|
|
" return false;\n",
|
|
" });\n",
|
|
"\n",
|
|
" function set_focus () {\n",
|
|
" canvas.focus();\n",
|
|
" canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" window.setTimeout(set_focus, 100);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>')\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" for(var toolbar_ind in mpl.toolbar_items) {\n",
|
|
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
|
|
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
|
|
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
|
|
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
|
|
"\n",
|
|
" if (!name) {\n",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option)\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
|
|
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
|
|
" // which will in turn request a refresh of the image.\n",
|
|
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.send_message = function(type, properties) {\n",
|
|
" properties['type'] = type;\n",
|
|
" properties['figure_id'] = this.id;\n",
|
|
" this.ws.send(JSON.stringify(properties));\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.send_draw_message = function() {\n",
|
|
" if (!this.waiting) {\n",
|
|
" this.waiting = true;\n",
|
|
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
|
|
" var format_dropdown = fig.format_dropdown;\n",
|
|
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
|
|
" fig.ondownload(fig, format);\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
|
|
" var size = msg['size'];\n",
|
|
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
|
|
" fig._resize_canvas(size[0], size[1]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
|
|
" x0 = Math.floor(x0) + 0.5;\n",
|
|
" y0 = Math.floor(y0) + 0.5;\n",
|
|
" x1 = Math.floor(x1) + 0.5;\n",
|
|
" y1 = Math.floor(y1) + 0.5;\n",
|
|
" var min_x = Math.min(x0, x1);\n",
|
|
" var min_y = Math.min(y0, y1);\n",
|
|
" var width = Math.abs(x1 - x0);\n",
|
|
" var height = Math.abs(y1 - y0);\n",
|
|
"\n",
|
|
" fig.rubberband_context.clearRect(\n",
|
|
" 0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
|
|
" fig.imageObj.src = evt.data;\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var msg = JSON.parse(evt.data);\n",
|
|
" var msg_type = msg['type'];\n",
|
|
"\n",
|
|
" // Call the \"handle_{type}\" callback, which takes\n",
|
|
" // the figure and JSON message as its only arguments.\n",
|
|
" try {\n",
|
|
" var callback = fig[\"handle_\" + msg_type];\n",
|
|
" } catch (e) {\n",
|
|
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" if (callback) {\n",
|
|
" try {\n",
|
|
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
|
|
" callback(fig, msg);\n",
|
|
" } catch (e) {\n",
|
|
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" return obj;\n",
|
|
" }, {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
|
|
" var canvas_pos = mpl.findpos(event)\n",
|
|
"\n",
|
|
" if (name === 'button_press')\n",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\n",
|
|
"\n",
|
|
" /* This prevents the web browser from automatically changing to\n",
|
|
" * the text insertion cursor when the button is pressed. We want\n",
|
|
" * to control all of the cursor setting manually through the\n",
|
|
" * 'cursor' event from matplotlib */\n",
|
|
" event.preventDefault();\n",
|
|
" return false;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" // Handle any extra behaviour associated with a key event\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.key_event = function(event, name) {\n",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" guiEvent: simpleKeys(event)});\n",
|
|
" return false;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
|
|
" if (name == 'download') {\n",
|
|
" this.handle_save(this, null);\n",
|
|
" } else {\n",
|
|
" this.send_message(\"toolbar_button\", {name: name});\n",
|
|
" }\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
|
|
" this.message.textContent = tooltip;\n",
|
|
"};\n",
|
|
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overriden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\n",
|
|
"\n",
|
|
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
|
|
" // web socket which is closed, not our websocket->open comm proxy.\n",
|
|
" ws_proxy.onopen();\n",
|
|
"\n",
|
|
" fig.parent_element = element.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\n",
|
|
"\n",
|
|
" // Update the output cell to use the data from the current canvas.\n",
|
|
" fig.push_to_output();\n",
|
|
" var dataURL = fig.canvas.toDataURL();\n",
|
|
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
|
|
" // the notebook keyboard shortcuts fail.\n",
|
|
" IPython.keyboard_manager.enable()\n",
|
|
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>')\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" for(var toolbar_ind in mpl.toolbar_items){\n",
|
|
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
|
|
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
|
|
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
|
|
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
|
|
"\n",
|
|
" if (!name) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('tabindex', 0)\n",
|
|
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
|
|
" // off when our div gets focus\n",
|
|
"\n",
|
|
" // location in version 3\n",
|
|
" if (IPython.notebook.keyboard_manager) {\n",
|
|
" IPython.notebook.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // Check for shift+enter\n",
|
|
" if (event.shiftKey && event.which == 13) {\n",
|
|
" this.canvas_div.blur();\n",
|
|
" event.shiftKey = false;\n",
|
|
" // Send a \"J\" for go to next cell\n",
|
|
" event.which = 74;\n",
|
|
" event.keyCode = 74;\n",
|
|
" manager.command_mode();\n",
|
|
" manager.handle_keydown(event);\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
|
|
" fig.ondownload(fig, null);\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.find_output_cell = function(html_output) {\n",
|
|
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
|
|
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
|
|
" // IPython event is triggered only after the cells have been serialised, which for\n",
|
|
" // our purposes (turning an active figure into a static one), is too late.\n",
|
|
" var cells = IPython.notebook.get_cells();\n",
|
|
" var ncells = cells.length;\n",
|
|
" for (var i=0; i<ncells; i++) {\n",
|
|
" var cell = cells[i];\n",
|
|
" if (cell.cell_type === 'code'){\n",
|
|
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
|
|
" var data = cell.output_area.outputs[j];\n",
|
|
" if (data.data) {\n",
|
|
" // IPython >= 3 moved mimebundle to data attribute of output\n",
|
|
" data = data.data;\n",
|
|
" }\n",
|
|
" if (data['text/html'] == html_output) {\n",
|
|
" return [cell, data, j];\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
"}\n",
|
|
"\n",
|
|
"// Register the function which deals with the matplotlib target/channel.\n",
|
|
"// The kernel may be null if the page has been refreshed.\n",
|
|
"if (IPython.notebook.kernel != null) {\n",
|
|
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,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\" width=\"431.818172458775\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"ep reward: -0.00001785 [-0.00026605, 0.00020300], portfolio_value: 0.9711 mdd=1.86% sharpe=-1.1544, expl= 0.00% eps=2000 weights={'XMRBTC': 0.27570000290870667, 'DASHBTC': 0.21209999918937683, 'LTCBTC': 0.3009999990463257}\n",
|
|
"ep reward: 0.00000532 [-0.00078238, 0.00042519], portfolio_value: 1.0088 mdd=4.64% sharpe=0.1644, expl= 0.00% eps=3000 weights={'XMRBTC': 0.1890999972820282, 'DASHBTC': 0.23909999430179596, 'LTCBTC': 0.36410000920295715}\n",
|
|
"ep reward: -0.00000729 [-0.00021715, 0.00014452], portfolio_value: 0.9881 mdd=1.26% sharpe=-0.4783, expl= 0.00% eps=4000 weights={'XMRBTC': 0.1940000057220459, 'DASHBTC': 0.32100000977516174, 'LTCBTC': 0.25619998574256897}\n",
|
|
"ep reward: -0.00001573 [-0.00032264, 0.00035352], portfolio_value: 0.9745 mdd=2.56% sharpe=-0.6287, expl= 0.00% eps=5000 weights={'XMRBTC': 0.2939000129699707, 'DASHBTC': 0.22059999406337738, 'LTCBTC': 0.2689000070095062}\n",
|
|
"ep reward: -0.00001694 [-0.00018788, 0.00044721], portfolio_value: 0.9726 mdd=2.51% sharpe=-0.7508, expl= 0.00% eps=6000 weights={'XMRBTC': 0.26840001344680786, 'DASHBTC': 0.26570001244544983, 'LTCBTC': 0.18019999563694}\n",
|
|
"ep reward: 0.00001670 [-0.00025710, 0.00031601], portfolio_value: 1.0278 mdd=2.25% sharpe=0.6204, expl= 0.00% eps=7000 weights={'XMRBTC': 0.30649998784065247, 'DASHBTC': 0.31540000438690186, 'LTCBTC': 0.2093999981880188}\n",
|
|
"ep reward: -0.00000421 [-0.00019934, 0.00017192], portfolio_value: 0.9931 mdd=1.47% sharpe=-0.3470, expl= 0.00% eps=8000 weights={'XMRBTC': 0.2502000033855438, 'DASHBTC': 0.2231999933719635, 'LTCBTC': 0.26660001277923584}\n",
|
|
"ep reward: -0.00000977 [-0.00028113, 0.00036908], portfolio_value: 0.9841 mdd=2.57% sharpe=-0.5455, expl= 0.00% eps=9000 weights={'XMRBTC': 0.22669999301433563, 'DASHBTC': 0.2973000109195709, 'LTCBTC': 0.1964000016450882}\n",
|
|
"ep reward: -0.00001618 [-0.00027920, 0.00026605], portfolio_value: 0.9738 mdd=2.16% sharpe=-0.9461, expl= 0.00% eps=10000 weights={'XMRBTC': 0.265500009059906, 'DASHBTC': 0.30720001459121704, 'LTCBTC': 0.20029999315738678}\n",
|
|
"ep reward: 0.00000871 [-0.00036313, 0.00040417], portfolio_value: 1.0144 mdd=3.02% sharpe=0.3618, expl= 0.00% eps=11000 weights={'XMRBTC': 0.21439999341964722, 'DASHBTC': 0.2329999953508377, 'LTCBTC': 0.3199999928474426}\n",
|
|
"ep reward: 0.00001219 [-0.00022213, 0.00053127], portfolio_value: 1.0202 mdd=1.69% sharpe=0.5442, expl= 0.00% eps=12000 weights={'XMRBTC': 0.33219999074935913, 'DASHBTC': 0.2184000015258789, 'LTCBTC': 0.2621000111103058}\n",
|
|
"ep reward: 0.00000874 [-0.00011436, 0.00029182], portfolio_value: 1.0144 mdd=1.61% sharpe=0.5911, expl= 0.00% eps=13000 weights={'XMRBTC': 0.27309998869895935, 'DASHBTC': 0.2515999972820282, 'LTCBTC': 0.22550000250339508}\n",
|
|
"ep reward: -0.00000926 [-0.00033684, 0.00022952], portfolio_value: 0.9849 mdd=2.07% sharpe=-0.3652, expl= 0.00% eps=14000 weights={'XMRBTC': 0.2777000069618225, 'DASHBTC': 0.26600000262260437, 'LTCBTC': 0.21410000324249268}\n",
|
|
"ep reward: 0.00000333 [-0.00021975, 0.00040941], portfolio_value: 1.0055 mdd=2.17% sharpe=0.1844, expl= 0.00% eps=15000 weights={'XMRBTC': 0.22100000083446503, 'DASHBTC': 0.20170000195503235, 'LTCBTC': 0.2554999887943268}\n",
|
|
"ep reward: -0.00000750 [-0.00039026, 0.00025949], portfolio_value: 0.9878 mdd=1.81% sharpe=-0.3766, expl= 0.00% eps=16000 weights={'XMRBTC': 0.22280000150203705, 'DASHBTC': 0.3804999887943268, 'LTCBTC': 0.16990000009536743}\n",
|
|
"ep reward: -0.00001127 [-0.00013257, 0.00018251], portfolio_value: 0.9817 mdd=1.25% sharpe=-0.9329, expl= 0.00% eps=17000 weights={'XMRBTC': 0.23489999771118164, 'DASHBTC': 0.26829999685287476, 'LTCBTC': 0.28870001435279846}\n",
|
|
"ep reward: -0.00001444 [-0.00034051, 0.00021181], portfolio_value: 0.9766 mdd=2.19% sharpe=-0.6838, expl= 0.00% eps=18000 weights={'XMRBTC': 0.14830000698566437, 'DASHBTC': 0.30390000343322754, 'LTCBTC': 0.18780000507831573}\n",
|
|
"ep reward: -0.00000773 [-0.00017357, 0.00025191], portfolio_value: 0.9874 mdd=1.69% sharpe=-0.4061, expl= 0.00% eps=19000 weights={'XMRBTC': 0.15800000727176666, 'DASHBTC': 0.29010000824928284, 'LTCBTC': 0.29409998655319214}\n",
|
|
"ep reward: -0.00001856 [-0.00022778, 0.00019409], portfolio_value: 0.9700 mdd=1.51% sharpe=-1.0007, expl= 0.00% eps=20000 weights={'XMRBTC': 0.2037999927997589, 'DASHBTC': 0.3158000111579895, 'LTCBTC': 0.26089999079704285}\n",
|
|
"ep reward: -0.00000923 [-0.00019480, 0.00019527], portfolio_value: 0.9850 mdd=1.38% sharpe=-0.6350, expl= 0.00% eps=21000 weights={'XMRBTC': 0.3695000112056732, 'DASHBTC': 0.1858000010251999, 'LTCBTC': 0.18199999630451202}\n",
|
|
"ep reward: -0.00000986 [-0.00020236, 0.00022601], portfolio_value: 0.9840 mdd=1.70% sharpe=-0.5568, expl= 0.00% eps=22000 weights={'XMRBTC': 0.14100000262260437, 'DASHBTC': 0.2295999974012375, 'LTCBTC': 0.303600013256073}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"%matplotlib notebook\n",
|
|
"steps=12e6\n",
|
|
"env._plot = env._plot2 = env._plot3 = None\n",
|
|
"episodes = int(steps / 30)\n",
|
|
"runner.run(\n",
|
|
" timesteps=steps,\n",
|
|
" episode_finished=EpisodeFinishedTQDM(\n",
|
|
" log_intv=1000,\n",
|
|
" steps=steps,\n",
|
|
" mean_of=1000,\n",
|
|
" log_dir=log_dir,\n",
|
|
" session=runner.agent.model.session,\n",
|
|
" )\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2017-11-12T06:07:24.082729Z",
|
|
"start_time": "2017-11-12T06:07:24.038681Z"
|
|
},
|
|
"collapsed": true
|
|
},
|
|
"source": [
|
|
"# History"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.518Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# env.reset()\n",
|
|
"# for i in range(1000):\n",
|
|
"# action = env.unwrapped.action_space.sample()\n",
|
|
"# state, reward, done, info = env.step(action)\n",
|
|
"# if done:\n",
|
|
"# env.reset()\n",
|
|
"# assert state.shape==(3,51,4), '%s %s'%(state.shape, i)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.523Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# env.unwrapped.src.step, env.unwrapped.src.steps, env.unwrapped.src.idx, env.unwrapped.src.data.shape, env.unwrapped.src._data.shape, 46993-env.unwrapped.src.window_length+env.unwrapped.src.steps\n",
|
|
"# # env.unwrapped.src.window_length+env.unwrapped.src.steps"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T06:03:12.521413Z",
|
|
"start_time": "2018-02-18T06:03:12.488215Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.533Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# read tensorboard history, might be slow\n",
|
|
"import collections\n",
|
|
"import glob\n",
|
|
"from tensorflow.python.summary import summary_iterator\n",
|
|
"event_paths = glob.glob(os.path.join(log_dir, \"event*\"))\n",
|
|
"data = collections.defaultdict(dict)\n",
|
|
"for file in event_paths:\n",
|
|
" event_reader = summary_iterator.summary_iterator(file)\n",
|
|
" try:\n",
|
|
" for event in tqdm(event_reader):\n",
|
|
" try:\n",
|
|
" step=event.step\n",
|
|
" name=event.summary.value[0].tag\n",
|
|
" val=event.summary.value[0].simple_value\n",
|
|
" data[step][name]=val\n",
|
|
" except:\n",
|
|
" pass\n",
|
|
" except tf.errors.DataLossError as e:\n",
|
|
" print('data ended early', tf.errors.DataLossError)\n",
|
|
" \n",
|
|
" "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2017-11-12T06:06:52.676576Z",
|
|
"start_time": "2017-11-12T06:06:52.632828Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.541Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# View history\n",
|
|
"df = pd.DataFrame(data).T\n",
|
|
"df\n",
|
|
"\n",
|
|
"df[\"steps\"]=df.index\n",
|
|
"plt.figure(figsize=(8,8))\n",
|
|
"sns.regplot(x=\"steps\", y=\"reward\", data=df, order=1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T03:44:26.480850Z",
|
|
"start_time": "2018-02-18T03:40:56.686Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2017-11-11T14:18:45.083902Z",
|
|
"start_time": "2017-11-11T14:18:45.054430Z"
|
|
}
|
|
},
|
|
"source": [
|
|
"# Test"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.548Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"df_test = pd.read_hdf('./data/poloniex_30m.hf',key='test')\n",
|
|
"env_test = PortfolioEnv(\n",
|
|
" df=df_test,\n",
|
|
" steps=48*7, # run for a week at a time\n",
|
|
" scale=True, \n",
|
|
" trading_cost=0.0025, \n",
|
|
" window_length = window_length,\n",
|
|
" output_mode='EIIE',\n",
|
|
")\n",
|
|
"# wrap it in a few wrappers\n",
|
|
"env_test = ConcatStates(env_test)\n",
|
|
"env_test = SoftmaxActions(env_test)\n",
|
|
"environment_test = TFOpenAIGymCust('CryptoPortfolioEIIETest-v0', env_test)\n",
|
|
"\n",
|
|
"env_test.seed(0)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.553Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"runner_test = Runner(agent=agent, environment=environment_test)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.559Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def episode_finished(r):\n",
|
|
" print(\"Finished episode {ep} after {ts} timesteps (reward: {reward})\".format(ep=r.episode, ts=r.episode_timestep,\n",
|
|
" reward=r.episode_rewards[-1]))\n",
|
|
" return True"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.564Z"
|
|
},
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"episodes=1\n",
|
|
"steps=environment_test.gym.env.env.src.steps*episodes\n",
|
|
"runner_test.run(\n",
|
|
" episodes=episodes,\n",
|
|
" timesteps=steps,\n",
|
|
" deterministic=True,\n",
|
|
" episode_finished=episode_finished,\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"start_time": "2018-02-18T06:04:01.570Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# plot\n",
|
|
"%matplotlib inline\n",
|
|
"env_test.unwrapped.render('notebook', close=True)\n",
|
|
"env_test.unwrapped.render('notebook')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"ExecuteTime": {
|
|
"end_time": "2018-02-18T03:17:22.315566Z",
|
|
"start_time": "2018-02-18T03:17:21.636748Z"
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "jupyter3",
|
|
"language": "python",
|
|
"name": "jupyter3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.5.3"
|
|
},
|
|
"toc": {
|
|
"colors": {
|
|
"hover_highlight": "#DAA520",
|
|
"navigate_num": "#000000",
|
|
"navigate_text": "#333333",
|
|
"running_highlight": "#FF0000",
|
|
"selected_highlight": "#FFD700",
|
|
"sidebar_border": "#EEEEEE",
|
|
"wrapper_background": "#FFFFFF"
|
|
},
|
|
"moveMenuLeft": true,
|
|
"nav_menu": {
|
|
"height": "146px",
|
|
"width": "252px"
|
|
},
|
|
"navigate_menu": true,
|
|
"number_sections": true,
|
|
"sideBar": true,
|
|
"threshold": 4,
|
|
"toc_cell": false,
|
|
"toc_section_display": "block",
|
|
"toc_window_display": true,
|
|
"widenNotebook": false
|
|
},
|
|
"varInspector": {
|
|
"cols": {
|
|
"lenName": 16,
|
|
"lenType": 16,
|
|
"lenVar": 40
|
|
},
|
|
"kernels_config": {
|
|
"python": {
|
|
"delete_cmd_postfix": "",
|
|
"delete_cmd_prefix": "del ",
|
|
"library": "var_list.py",
|
|
"varRefreshCmd": "print(var_dic_list())"
|
|
},
|
|
"r": {
|
|
"delete_cmd_postfix": ") ",
|
|
"delete_cmd_prefix": "rm(",
|
|
"library": "var_list.r",
|
|
"varRefreshCmd": "cat(var_dic_list()) "
|
|
}
|
|
},
|
|
"types_to_exclude": [
|
|
"module",
|
|
"function",
|
|
"builtin_function_or_method",
|
|
"instance",
|
|
"_Feature"
|
|
],
|
|
"window_display": false
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|