PKapI] jjbowtie/__init__.py""" Bowtie """ __version__ = '0.0.11' from flask_socketio import emit from bowtie._layout import Layout PK$)I"bowtie/_compat.py# -*- coding: utf-8 -*- """ python 2/3 compatability """ import sys from os import makedirs IS_PY2 = sys.version_info < (3, 0) IS_PY35 = sys.version_info >= (3, 5) if IS_PY2: # pylint: disable=invalid-name makedirs_lib = makedirs # pylint: disable=function-redefined,missing-docstring def makedirs(name, mode=0o777, exist_ok=False): try: makedirs_lib(name, mode=mode) except OSError: if not exist_ok: raise PK^pIG6 bowtie/_component.py# -*- coding: utf-8 -*- """ Bowtie Component classes, all visual and control components inherit these """ from __future__ import unicode_literals from builtins import bytes import json import msgpack from datetime import datetime, date, time import flask from flask_socketio import emit from future.utils import with_metaclass from eventlet.event import Event from eventlet.queue import LightQueue from bowtie._compat import IS_PY35 def json_conversion(obj): try: # numpy isn't an explicit dependency of bowtie # so we can't assume it's available import numpy as np if isinstance(obj, np.ndarray): return obj.tolist() except ImportError: pass if isinstance(obj, datetime) or isinstance(obj, time) or isinstance(obj, date): return obj.isoformat() raise TypeError('Not sure how to serialize {} of type {}'.format(obj, type(obj))) def jdumps(data): return json.dumps(data, default=json_conversion) def encoders(obj): try: # numpy isn't an explicit dependency of bowtie # so we can't assume it's available import numpy as np if isinstance(obj, np.ndarray): return obj.tolist() except ImportError: pass if isinstance(obj, datetime) or isinstance(obj, time) or isinstance(obj, date): return obj.isoformat() return obj def pack(x): return bytes(msgpack.packb(x, default=encoders)) def make_event(event): @property def actualevent(self): name = event.__name__[3:] return '{uuid}#{event}'.format(uuid=self._uuid, event=name) if IS_PY35: # can't set docstring on properties in python 2 like this actualevent.__doc__ = event.__doc__ return actualevent def is_event(attribute): return attribute.startswith('on_') def make_command(command): def actualcommand(self, data): name = command.__name__[3:] signal = '{uuid}#{event}'.format(uuid=self._uuid, event=name) if flask.has_request_context(): return emit(signal, {'data': pack(data)}) else: sio = flask.current_app.extensions['socketio'] return sio.emit(signal, {'data': pack(data)}) actualcommand.__doc__ = command.__doc__ return actualcommand def is_command(attribute): return attribute.startswith('do_') class _Maker(type): def __new__(cls, name, parents, dct): for k in dct: if is_event(k): dct[k] = make_event(dct[k]) if is_command(k): dct[k] = make_command(dct[k]) return super(_Maker, cls).__new__(cls, name, parents, dct) class Component(with_metaclass(_Maker, object)): _NEXT_UUID = 0 @classmethod def _next_uuid(cls): cls._NEXT_UUID += 1 return cls._NEXT_UUID def __init__(self): # wanted to put "self" instead of "Component" # was surprised that didn't work self._uuid = Component._next_uuid() super(Component, self).__init__() def get(self, block=True, timeout=None): event = LightQueue(1) if flask.has_request_context(): emit('{}#get'.format(self._uuid), callback=lambda x: event.put(msgpack.unpackb(bytes(x['data'])))) else: sio = flask.current_app.extensions['socketio'] sio.emit('{}#get'.format(self._uuid), callback=lambda x: event.put(msgpack.unpackb(bytes(x['data'])))) return event.get(timeout=10) PKwoI8޳!!bowtie/_layout.py# -*- coding: utf-8 -*- import os from os import path import stat from subprocess import Popen import inspect from flask import Markup from collections import namedtuple, defaultdict from jinja2 import Environment, FileSystemLoader from markdown import markdown from bowtie._compat import makedirs from bowtie.control import _Controller from bowtie.visual import _Visual _Import = namedtuple('_Import', ['module', 'component']) _Control = namedtuple('_Control', ['instantiate', 'caption']) _Schedule = namedtuple('_Control', ['seconds', 'function']) class NPMError(Exception): pass class WebpackError(Exception): pass class Layout(object): """Create a Bowtie App. Parameters ---------- title : str, optional Title of the HTML. description : str, optional Describe the app in Markdown, inserted in control pane. basic_auth : bool, optional Enable basic authentication. username : str, optional Username for basic authentication. password : str, optional Password for basic authentication. background_color : str, optional Background color of the control pane. directory : str, optional Location where app is compiled. host : str, optional Host IP address. port : int, optional Host port number. debug : bool, optional Enable debugging in Flask. Disable in production! """ _packages = [ 'babel-core', 'babel-loader', 'babel-plugin-transform-object-rest-spread', 'babel-polyfill', 'babel-preset-es2015', 'babel-preset-react', 'babel-preset-stage-0', 'classnames', 'core-js', 'css-loader', 'extract-text-webpack-plugin', 'less-loader', 'lodash.clonedeep', 'msgpack-lite', 'node-sass', 'normalize.css', 'postcss-modules-values', 'react', 'react-dom', 'sass-loader', 'socket.io-client', 'style-loader', 'webpack@1.13.2' ] def __init__(self, title='Bowtie App', description='Bowtie App\n---', basic_auth=False, username='username', password='password', background_color='White', directory='build', host='0.0.0.0', port=9991, debug=False): self.title = title self.description = Markup(markdown(description)) self.basic_auth = basic_auth self.username = username self.password = password self.background_color = background_color self.directory = directory self.host = host self.port = port self.debug = debug self.subscriptions = defaultdict(list) self.packages = set() self.templates = set() self.imports = set() self.visuals = [[]] self.controllers = [] self.schedules = [] self.functions = [] def add_visual(self, visual, width=None, height=None, width_pixels=None, height_pixels=None, next_row=False): """Add a visual to the layout. Parameters ---------- visual : bowtie._Visual A Bowtie visual instance. next_row : bool, optional Add this visual to the next row. """ assert isinstance(visual, _Visual) self.packages.add(visual._PACKAGE) self.templates.add(visual._TEMPLATE) self.imports.add(_Import(component=visual._COMPONENT, module=visual._TEMPLATE[:visual._TEMPLATE.find('.')])) if next_row and self.visuals[-1]: self.visuals.append([]) self.visuals[-1].append(visual) def add_controller(self, control): """Add a controller to the layout. Parameters ---------- control : bowtie._Controller A Bowtie controller instance. """ pass assert isinstance(control, _Controller) self.packages.add(control._PACKAGE) self.templates.add(control._TEMPLATE) self.imports.add(_Import(component=control._COMPONENT, module=control._TEMPLATE[:control._TEMPLATE.find('.')])) self.controllers.append(_Control(instantiate=control._instantiate, caption=control.caption)) def subscribe(self, event, func): """Call a function in response to an event. Parameters ---------- event : str Name of the event. func : callable Function to be called. """ e = "'{}'".format(event) self.subscriptions[e].append(func.__name__) def schedule(self, seconds, func): """Call a function periodically. Parameters ---------- seconds : float Minimum interval of function calls. func : callable Function to be called. """ self.schedules.append(_Schedule(seconds, func.__name__)) def build(self): """Compiles the Bowtie application. """ env = Environment(loader=FileSystemLoader( path.join(path.dirname(__file__), 'templates') )) webpack = env.get_template('webpack.config.js') server = env.get_template('server.py') index = env.get_template('index.html') react = env.get_template('index.jsx') src, app, templates = create_directories(directory=self.directory) with open(path.join(self.directory, webpack.name), 'w') as f: f.write( webpack.render() ) server_path = path.join(src, server.name) # [1] grabs the parent stack and [1] grabs the filename source_filename = inspect.stack()[1][1] with open(server_path, 'w') as f: f.write( server.render( basic_auth=self.basic_auth, username=self.username, password=self.password, source_module=os.path.basename(source_filename)[:-3], subscriptions=self.subscriptions, schedules=self.schedules, host="'{}'".format(self.host), port=self.port, debug=self.debug ) ) perms = os.stat(server_path) os.chmod(server_path, perms.st_mode | stat.S_IEXEC) with open(path.join(templates, index.name), 'w') as f: f.write( index.render(title=self.title) ) # components = [env.get_template(t).render() for t in self.templates] for template in self.templates: temp = env.get_template(template) with open(path.join(app, temp.name), 'w') as f: f.write( temp.render() ) for i, visualrow in enumerate(self.visuals): for j, visual in enumerate(visualrow): self.visuals[i][j] = self.visuals[i][j]._instantiate() # columns=len(visualrow), # rows=len(self.visuals) # ) with open(path.join(app, react.name), 'w') as f: f.write( react.render( description=self.description, background_color=self.background_color, components=self.imports, controls=self.controllers, visuals=self.visuals ) ) init = Popen('npm init -f', shell=True, cwd=self.directory).wait() if init != 0: raise NPMError('Error running "npm init -f"') self.packages.discard(None) packages = ' '.join(self._packages + list(self.packages)) install = Popen('yarn add {}'.format(packages), shell=True, cwd=self.directory).wait() # install = Popen('npm install -S {}'.format(packages), # shell=True, cwd='build').wait() if install != 0: raise NPMError('Error install node packages') dev = Popen('webpack -d', shell=True, cwd=self.directory).wait() if dev != 0: raise WebpackError('Error building with webpack') def create_directories(directory='build'): src = path.join(directory, 'src') templates = path.join(src, 'templates') app = path.join(src, 'app') makedirs(app, exist_ok=True) makedirs(templates, exist_ok=True) return src, app, templates PKvoI6옝 bowtie/control.py# -*- coding: utf-8 -*- """ Control components """ from collections import Iterable from bowtie._component import Component, jdumps # pylint: disable=too-few-public-methods class _Controller(Component): """ Used to test if a an object is a controller. All controllers must inherit this class. """ pass class Button(_Controller): _TEMPLATE = 'button.jsx' _COMPONENT = 'SimpleButton' _PACKAGE = None _TAG = ('') def __init__(self, label='', caption=''): super(Button, self).__init__() self._instantiate = self._TAG.format( label="'{}'".format(label), uuid="'{}'".format(self._uuid) ) self.caption = caption def on_click(self): pass class DropDown(_Controller): _TEMPLATE = 'dropdown.jsx' _COMPONENT = 'DropDown' _PACKAGE = 'react-select' _TAG = ('') def __init__(self, labels=None, values=None, multi=False, caption=''): super(DropDown, self).__init__() if labels is None and values is None: labels = [] values = [] options = [dict(value=value, label=str(label)) for value, label in zip(values, labels)] self._instantiate = self._TAG.format( options=jdumps(options), multi='true' if multi else 'false', uuid="'{}'".format(self._uuid) ) self.caption = caption def on_change(self): """Emits an event when the selection changes. | **Payload:** `dict` with keys "value" and "label". """ pass def do_options(self, data): pass class Nouislider(_Controller): _TEMPLATE = 'nouislider.jsx' _COMPONENT = 'Nouislider' _PACKAGE = 'nouislider' _TAG = ('') def __init__(self, start=0, minimum=0, maximum=100, tooltips=True, caption=''): super(Nouislider, self).__init__() if not isinstance(start, Iterable): start = [start] else: start = list(start) self._instantiate = self._TAG.format( uuid="'{}'".format(self._uuid), min=minimum, max=maximum, start=start, tooltips='true' if tooltips else 'false' ) self.caption = caption def on_update(self): pass def on_slide(self): pass def on_set(self): pass def on_change(self): pass def on_start(self): pass def on_en(self): pass PK)SJInnbowtie/visual.py# -*- coding: utf-8 -*- """ Visual components """ from bowtie._component import Component, jdumps # pylint: disable=too-few-public-methods class _Visual(Component): """ Used to test if a an object is a controller. All controllers must inherit this class. """ pass class SmartGrid(_Visual): """Table Component with filtering and sorting Parameters ---------- columns : list, optional List of column names to display. results_per_page : int, optional Number of rows on each pagination of the table. """ _TEMPLATE = 'griddle.jsx' _COMPONENT = 'SmartGrid' _PACKAGE = 'griddle-react' _TAG = ('') def __init__(self, columns=None, results_per_page=10): if columns is None: columns = [] self.columns = columns self.results_per_page = results_per_page super(SmartGrid, self).__init__() def _instantiate(self): return self._TAG.format( uuid="'{}'".format(self._uuid), columns=jdumps(self.columns), results_per_page=self.results_per_page ) def do_update(self, data): pass # # TODO: these visuals are partially implemented # class FixedTable(_Visual): # _TEMPLATE = 'fixedtable.jsx' # _COMPONENT = 'FixedTable' # _PACKAGE = 'fixed-data-table' # _TAG = ('') # # def __init__(self): # super(FixedTable, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # rows=rows, # columns=columns # ) # # # class DataTable(_Visual): # _TEMPLATE = 'datatables.jsx' # _COMPONENT = 'JTable' # _PACKAGE = 'react-jquery-datatables' # _TAG = ('') # # def __init__(self): # super(DataTable, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # ) # # # class Grid(_Visual): # _TEMPLATE = 'dazzlegrid.jsx' # _COMPONENT = 'Grid' # _PACKAGE = 'react-data-grid' # _TAG = ('') # # def __init__(self): # super(Grid, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # ) # # # class Table(_Visual): # _TEMPLATE = 'datagrid.jsx' # _COMPONENT = 'Table' # _PACKAGE = 'react-datagrid' # _TAG = ('') # # def __init__(self): # super(Table, self).__init__() # # def _instantiate(self, columns, rows): # return self._TAG.format( # uuid="'{}'".format(self._uuid), # ) class Plotly(_Visual): """ Plotly component. """ _TEMPLATE = 'plotly.jsx' _COMPONENT = 'PlotlyPlot' _PACKAGE = 'plotly.js' _TAG = ('') def __init__(self, init=None): super(Plotly, self).__init__() if init is None: init = dict(data=[], layout={'autosize': False}) self.init = init def _instantiate(self): return self._TAG.format( uuid="'{}'".format(self._uuid), init=jdumps(self.init), ) ## Events def on_click(self): """Plotly click event. Returns ------- str Name of click event. """ pass def on_beforehover(self): pass def on_hover(self): pass def on_unhover(self): pass def on_select(self): pass ## Commands def do_all(self, data): pass def do_data(self, data): pass def do_layout(self, data): pass def do_config(self, data): pass PKoI1qbowtie/templates/button.jsximport React from 'react'; export default class SimpleButton extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick(event) { this.props.socket.emit(this.props.uuid + '#click'); } render() { return ( ); } } SimpleButton.propTypes = { label: React.PropTypes.string.isRequired, uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired }; PK)gInmGGbowtie/templates/datagrid.jsximport 'react-datagrid/index.css'; import React from 'react'; import DataGrid from 'react-datagrid'; export default class Table extends React.Component { render() { var data = [ { id: '1', firstName: 'John', lastName: 'Bobson'}, { id: '2', firstName: 'Bob', lastName: 'Mclaren'}, { id: '3', firstName: 'Bob', lastName: 'Mclaren'}, { id: '4', firstName: 'Bob', lastName: 'Mclaren'}, { id: '5', firstName: 'Bob', lastName: 'Mclaren'}, { id: '6', firstName: 'Bob', lastName: 'Mclaren'}, { id: '7', firstName: 'Bob', lastName: 'Mclaren'}, { id: '8', firstName: 'Bob', lastName: 'Mclaren'}, { id: '9', firstName: 'Bob', lastName: 'Mclaren'}, { id: '10', firstName: 'Bob', lastName: 'Mclaren'}, { id: '11', firstName: 'Bob', lastName: 'Mclaren'}, { id: '12', firstName: 'Bob', lastName: 'Mclaren'}, { id: '13', firstName: 'Bob', lastName: 'Mclaren'}, { id: '14', firstName: 'Bob', lastName: 'Mclaren'}, { id: '15', firstName: 'Bob', lastName: 'Mclaren'}, { id: '16', firstName: 'Bob', lastName: 'Mclaren'}, { id: '17', firstName: 'Bob', lastName: 'Mclaren'}, { id: '18', firstName: 'Bob', lastName: 'Mclaren'}, { id: '19', firstName: 'Bob', lastName: 'Mclaren'}, { id: '20', firstName: 'Bob', lastName: 'Mclaren'}, { id: '21', firstName: 'Bob', lastName: 'Mclaren'}, { id: '22', firstName: 'Bob', lastName: 'Mclaren'}, { id: '23', firstName: 'Bob', lastName: 'Mclaren'}, { id: '24', firstName: 'Bob', lastName: 'Mclaren'} ]; var columns = [ { name: 'firstName'}, { name: 'lastName'} ]; return ( ); } } Table.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired }; PK0\pI nbllbowtie/templates/dropdown.jsximport React from 'react'; import Select from 'react-select'; // Be sure to include styles at some point, probably during your bootstrapping import 'react-select/dist/react-select.css'; var msgpack = require('msgpack-lite'); export default class DropDown extends React.Component { constructor(props) { super(props); this.state = {value: null}; this.state.options = this.props.initOptions; this.handleChange = this.handleChange.bind(this); // this.getValue = this.getValue.bind(this); this.newOptions = this.newOptions.bind(this); } handleChange(value) { this.setState({value}); this.props.socket.emit(this.props.uuid + '#change', msgpack.encode(value)); } newOptions(data) { var arr = new Uint8Array(data['data']); this.setState({value: null, options: msgpack.decode(arr)}); } componentDidMount() { var socket = this.props.socket; var uuid = this.props.uuid; socket.on(uuid + '#get', this.getValue); socket.on(uuid + '#options', this.newOptions); } getValue = (data, fn) => { fn(msgpack.encode(this.state.value)); } render () { return (
{headers.map(function(name, i){ return ( {name}} cell={} width={100} /> ); })}
); } } // Col 1} // cell={Column 1 static content} // width={100} // /> // Col 2} // cell={Column 2 static content} // width={100} // /> // Col 3} // cell={({rowIndex, ...props}) => ( // // Data for column 3: {this.state.data[rowIndex][2]} // // )} // width={100} // /> FixedTable.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, rows: React.PropTypes.number, columns: React.PropTypes.number }; PK0\pIA##bowtie/templates/griddle.jsximport React from 'react'; import Griddle from 'griddle-react'; var msgpack = require('msgpack-lite'); function get_height_width() { var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0], x = w.innerWidth || e.clientWidth || g.clientWidth, y = w.innerHeight|| e.clientHeight|| g.clientHeight; return [x, y]; } export default class SmartGrid extends React.Component { constructor(props) { super(props); this.state = {}; this.state.data = []; this.getData = this.getData.bind(this); } getData(data, fn) { fn(msgpack.encode(this.state.data)); } componentDidMount() { var socket = this.props.socket; socket.on(this.props.uuid + '#update', (data) => { var arr = new Uint8Array(data['data']); this.setState({data: msgpack.decode(data)}); }); socket.on(this.props.uuid + '#get', this.getData); } render() { return ( ); } } SmartGrid.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, columns: React.PropTypes.array.isRequired, resultsPerPage: React.PropTypes.number.isRequired }; PK)gIEEbowtie/templates/index.html {{ title }}
PK0I'bowtie/templates/index.jsximport 'normalize.css'; import React from 'react'; import {render} from 'react-dom'; import io from 'socket.io-client'; {% for component in components %} import {{ component.component }} from './{{ component.module }}'; {% endfor %} var socket = io(); class Dashboard extends React.Component { render() { return (
{{ description }} {% for control in controls %}
{{ control.caption }}
{{ control.instantiate }}
{% endfor %}
{% for visualrow in visuals %}
{% for visual in visualrow %}
{{ visual }}
{% endfor %}
{% endfor %}
); } } render(, document.getElementById('app')); PK0\pI-F`HHHbowtie/templates/nouislider.jsximport React from 'react'; // import 'nouislider-algolia-fork/src/nouislider.css'; // import 'nouislider-algolia-fork/src/nouislider.css'; import 'nouislider/src/nouislider.css'; // import Nouislider from 'react-nouislider'; // import nouislider from 'nouislider-algolia-fork'; import nouislider from 'nouislider'; var msgpack = require('msgpack-lite'); function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } export default class Nouislider extends React.Component { constructor(props) { super(props); this.createSlider = this.createSlider.bind(this); this.getValue = this.getValue.bind(this); } createSlider() { var slider = this.slider = nouislider.create(this.sliderContainer, {...this.props} ); var uuid = this.props.uuid; var socket = this.props.socket; slider.on('update', function (data) { socket.emit(uuid + '#update', data); }); // if (this.props.onChange) { slider.on('change', function (data) { socket.emit(uuid + '#change', data); }); // // // if (this.props.onSlide) { slider.on('slide', function (data) { socket.emit(uuid + '#slide', data); }); // {..._objectWithoutProperties(this.props, ["uuid"])} // if (this.props.onUpdate) { } componentDidMount() { if (this.props.disabled) this.sliderContainer.setAttribute('disabled', true); else this.sliderContainer.removeAttribute('disabled'); this.createSlider(); var uuid = this.props.uuid; var socket = this.props.socket; socket.on(uuid + '#get', this.getValue); } getValue(data, fn) { fn(msgpack.encode(this.slider.get())); } componentDidUpdate() { if (this.props.disabled) this.sliderContainer.setAttribute('disabled', true); else this.sliderContainer.removeAttribute('disabled'); this.slider.destroy(); this.createSlider(); } componentWillUnmount() { this.slider.destroy(); } render() { return (
this.sliderContainer = slider} /> ); } } Nouislider.propTypes = { // http://refreshless.com/nouislider/slider-options/#section-animate uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, animate: React.PropTypes.bool, // http://refreshless.com/nouislider/behaviour-option/ behaviour: React.PropTypes.string, // http://refreshless.com/nouislider/slider-options/#section-Connect connect: React.PropTypes.oneOfType([ React.PropTypes.oneOf(['lower', 'upper']), React.PropTypes.bool ]), // http://refreshless.com/nouislider/slider-options/#section-cssPrefix cssPrefix: React.PropTypes.string, // http://refreshless.com/nouislider/slider-options/#section-orientation direction: React.PropTypes.oneOf(['ltr', 'rtl']), // http://refreshless.com/nouislider/more/#section-disable disabled: React.PropTypes.bool, // http://refreshless.com/nouislider/slider-options/#section-limit limit: React.PropTypes.number, // http://refreshless.com/nouislider/slider-options/#section-margin margin: React.PropTypes.number, // http://refreshless.com/nouislider/events-callbacks/#section-change // onChange: React.PropTypes.func, // http://refreshless.com/nouislider/events-callbacks/#section-update // onSlide: React.PropTypes.func, // http://refreshless.com/nouislider/events-callbacks/#section-slide // onUpdate: React.PropTypes.func, // http://refreshless.com/nouislider/slider-options/#section-orientation orientation: React.PropTypes.oneOf(['horizontal', 'vertical']), // http://refreshless.com/nouislider/pips/ pips: React.PropTypes.object, // http://refreshless.com/nouislider/slider-values/#section-range range: React.PropTypes.object.isRequired, // http://refreshless.com/nouislider/slider-options/#section-start start: React.PropTypes.arrayOf(React.PropTypes.number).isRequired, // http://refreshless.com/nouislider/slider-options/#section-step step: React.PropTypes.number, // http://refreshless.com/nouislider/slider-options/#section-tooltips tooltips: React.PropTypes.oneOfType([ React.PropTypes.bool, React.PropTypes.arrayOf( React.PropTypes.shape({ to: React.PropTypes.func }) ) ]) }; PK0\pIsoPQQbowtie/templates/plotly.jsximport React from 'react'; import Plotly from 'plotly.js'; import cloneDeep from 'lodash.clonedeep'; var msgpack = require('msgpack-lite'); function get_height_width() { var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0], x = w.innerWidth || e.clientWidth || g.clientWidth, y = w.innerHeight|| e.clientHeight|| g.clientHeight; return [x, y]; } export default class PlotlyPlot extends React.Component { selection = 'inital'; constructor(props) { super(props); this.state = this.props.initState; this.resize = this.resize.bind(this); } shouldComponentUpdate(nextProps) { return true; } setSelection = data => { this.selection = data; this.props.socket.emit(this.props.uuid + '#select', msgpack.encode(data)); } getSelection = (data, fn) => { fn(msgpack.encode(this.selection)); } resize() { Plotly.Plots.resize(this.container); } addListeners() { this.container.on('plotly_click', function (data) { var p0 = data.points[0]; var datum = { n: p0.pointNumber, x: p0.x, y: p0.y, }; socket.emit(uuid + '#click', msgpack.encode(datum)); }); // if (this.props.onBeforeHover) // this.container.on('plotly_beforehover', function (data) { // socket.emit(uuid + '#beforehover', data); // }); // // if (this.props.onHover) // this.container.on('plotly_hover', function (data) { // socket.emit(uuid + '#hover', data); // }); // // if (this.props.onUnHover) // this.container.on('plotly_unhover', function (data) { // socket.emit(uuid + '#unhover', data); // }); // if (this.props.onSelected) this.container.on('plotly_selected', this.setSelection); } componentDidMount() { // let {data, layout, config} = this.props; var parent = window.getComputedStyle(this.container.parentElement); this.state.layout['autosize'] = false; this.state.layout['height'] = parseFloat(parent.height); this.state.layout['width'] = parseFloat(parent.width); Plotly.newPlot(this.container, this.state.data, cloneDeep(this.state.layout), {autosizable: false, displaylogo: false, fillFrame: true}); //, config); // if (this.props.onClick) var uuid = this.props.uuid; var socket = this.props.socket; // if (this.props.onClick) socket.on(this.props.uuid + '#all', (data) => { var arr = new Uint8Array(data['data']); this.setState(msgpack.decode(arr)); }); // socket.on(this.props.uuid + '#' + 'get', (data) => { // console.log('get command!!!'); // console.log(data); // console.log(uuid + '#put'); // socket.emit(uuid + '#put', [3]); //this.state); // console.log('done seding'); // }); socket.on(this.props.uuid + '#get', this.getSelection); this.addListeners(); // console.log('get command!!!'); // console.log(data); // console.log(uuid + '#put'); // socket.emit(uuid + '#put'); //this.state); // fn(this.state.selection); // console.log('done seding'); // }); // socket.emit(this.props.uuid + '#put', [3]); } // updateState(state) { // console.log('updating...'); // this.state = state; // } // updateState = (ev) => this.setState({ text: ev.target.value }); componentDidUpdate() { //TODO use minimal update for given changes // this.container.data = this.state.data; // this.container.layout = this.state.layout; // var hw = get_height_width(); // this.state.layout = this.state.layout || {}; // this.state.layout['height'] = hw[1] / this.props.rows; // this.state.layout['width'] = (hw[0] * 9 / 10) / this.props.columns; //this.container.config = {autosizable: true, fillFrame: true, displaylogo: false}; var parent = window.getComputedStyle(this.container.parentElement); this.state.layout['autosize'] = false; this.state.layout['height'] = parseFloat(parent.height); this.state.layout['width'] = parseFloat(parent.width); Plotly.newPlot(this.container, this.state.data, cloneDeep(this.state.layout), {autosizable: false, displaylogo: false, fillFrame: true}); //, config); this.addListeners(); // window.addEventListener('resize', this.resize); } componentWillUnmount() { Plotly.purge(this.container); } render() { return (
this.container=node} /> ); } } PlotlyPlot.propTypes = { uuid: React.PropTypes.string.isRequired, socket: React.PropTypes.object.isRequired, initState: React.PropTypes.object, rows: React.PropTypes.number, columns: React.PropTypes.number }; PK0\pIm bowtie/templates/server.py#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import traceback from functools import wraps from builtins import bytes import click import msgpack from flask import Flask, render_template, copy_current_request_context from flask import request, Response from flask_socketio import SocketIO, emit import eventlet def check_auth(username, password): """This function is called to check if a username / password combination is valid. """ return username == '{{ username }}' and password == '{{ password }}' def authenticate(): """Sends a 401 response that enables basic auth""" return Response( 'Could not verify your access level for that URL.\n' 'You have to login with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) return decorated # import the user created module sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import {{source_module}} app = Flask(__name__) app.debug = {{ debug|default(False) }} socketio = SocketIO(app, binary=True) def context(func): def foo(): with app.app_context(): func() return foo class Scheduler(object): def __init__(self, seconds, func): self.seconds = seconds self.func = func self.thread = None def start(self): self.thread = eventlet.spawn(self.run) def run(self): ret = eventlet.spawn(context(self.func)) eventlet.sleep(self.seconds) try: ret.wait() except: traceback.print_exc() self.thread = eventlet.spawn(self.run) def stop(self): if self.thread: self.thread.cancel() @app.route('/') {% if basic_auth %} @requires_auth {% endif %} def index(): return render_template('index.html') {% if login %} @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': success = {{ source_module }}.{{ login }}() if success: return redirect(url_for('index')) else: return redirect(url_for('login')) return {{ loginpage }} {% endif %} {% for event, functions in subscriptions.items() %} @socketio.on({{ event }}) def _(*args): {% for func in functions %} foo = copy_current_request_context({{ source_module }}.{{ func }}) eventlet.spawn(foo, *(msgpack.unpackb(bytes(a['data'])) for a in args)) {% endfor %} {% endfor %} @click.command() @click.option('--host', '-h', default={{host}}, help='Host IP') @click.option('--port', '-p', default={{port}}, help='port number') def main(host, port): scheds = [] {% for schedule in schedules %} sched = Scheduler({{ schedule.seconds }}, {{ source_module }}.{{ schedule.function }}) scheds.append(sched) {% endfor %} for sched in scheds: sched.start() socketio.run(app, host=host, port=port) for sched in scheds: sched.stop() if __name__ == '__main__': main() PK)gIy"bowtie/templates/webpack.config.jsvar webpack = require('webpack'); var path = require('path'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var extractCSS = new ExtractTextPlugin('stylesheets/[name].css'); var extractLESS = new ExtractTextPlugin('stylesheets/[name].less'); var BUILD_DIR = path.resolve(__dirname, 'src/static'); var APP_DIR = path.resolve(__dirname, 'src/app'); var config = { entry: APP_DIR + '/index.jsx', output: { path: BUILD_DIR, filename: 'bundle.js' }, module: { loaders: [ { test: /\.jsx?/, include: APP_DIR, loader: 'babel', exclude: /nodemodules/, query: { presets: ['es2015', 'react', 'stage-0'], plugins: ['transform-object-rest-spread'] } }, { test: /\.scss$/, loaders: ['style', 'css', 'sass'], exclude: /flexboxgrid/ }, { test: /\.css$/, loader: extractCSS.extract(['css', 'sass']), exclude: /flexboxgrid/ }, { test: /\.less$/, loader: extractLESS.extract(['less', 'sass']), }, { test: /\.(css|scss)$/, loader: 'style!css?modules', include: /flexboxgrid/ } ], noParse: [ /plotly\.js$/ ] }, plugins: [ extractCSS, extractLESS ], resolve: { extensions: ['', '.jsx', '.js', '.json'], modulesDirectories: [ 'node_modules', path.resolve(__dirname, './node_modules') ] } }; module.exports = config; PK]=Ibowtie/tests/__init__.pyPK0\pIνbowtie/tests/test_compile.py#!/usr/bin/env python # -*- coding: utf-8 -*- import os import shutil import pytest from bowtie import Layout from bowtie.control import Nouislider from bowtie.visual import Plotly @pytest.fixture def remove_build(request): yield path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') shutil.rmtree(path) def callback(*args): pass def test_build(remove_build): ctrl = Nouislider() viz = Plotly() path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') layout = Layout(directory=path) layout.add_controller(ctrl) layout.add_visual(viz) layout.subscribe(ctrl.on_change, callback) layout.build() PKoIW*((bowtie/tests/test_plotly.py#!/usr/bin/env python # -*- coding: utf-8 -*- import os import shutil import subprocess import pytest from selenium.webdriver import PhantomJS, ActionChains from bowtie import Layout from bowtie.control import Nouislider from bowtie.visual import Plotly @pytest.fixture def remove_build(request): yield path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') shutil.rmtree(path) def callback(*args): pass def test_plotly(remove_build): ctrl = Nouislider() viz = Plotly() path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'build') layout = Layout(directory=path) layout.add_controller(ctrl) layout.add_visual(viz) layout.subscribe(ctrl.on_change, callback) layout.build() env = os.environ env['PYTHONPATH'] = '{}:{}'.format(os.getcwd(), os.environ.get('PYTHONPATH', '')) rv = subprocess.Popen(os.path.join(path, 'src/server.py'), env=env) driver = PhantomJS() driver.get('http://localhost:9991') assert driver.title == 'Bowtie App' rv.kill() PK!H|&Ubbowtie-0.0.11.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,Q034 /, (-JLR()*M ILR(4KM̫#DPK!H՝j bowtie-0.0.11.dist-info/METADATAn0~ ߫$@[U=. $^peƒ`/t^h?24 )dwd鬿gE!de΍*jtM$G c]DO|*5{Q4%5x/SJňuf>}"sgtZĴhݜ]CmW~9qOqCHUu1:M2` 0HsV- 4}je{aW==PK!Hf]=,bowtie-0.0.11.dist-info/RECORDuɒJ}? T3`RY ( H"$8A/w*y]==g̺h#~bW!( s,-w (zx8=NK1 T 'L(%G#ޤ6|whmrGo_<+J(_9}dcEN ׏RkfsR7X.Q\104Х~f3Bh<8rs ӱګb/H쮉(]GTbG~%hmΑqk~ƕ^qNя} (QG8)m_/3W 00gތrY*M1W@i֕﮸ϠUkۭM?\G\J{^l1#QyR-FBWw|;cƑF7ł(o5:w/sY6ZVQAѹrӍTN%V0b|K0^PrJʤ/ABNA^¼v:1jsJ.YqN, ʼn$? 0!隶}ׄ,kDIs"#>:g*`𿦥*ҋ Ǒ>,rGIbsCC$00u=M=~ PKapI] jjbowtie/__init__.pyPK$)I"bowtie/_compat.pyPK^pIG6 bowtie/_component.pyPKwoI8޳!!bowtie/_layout.pyPKvoI6옝 2bowtie/control.pyPK)SJInn]>bowtie/visual.pyPKoI1qObowtie/templates/button.jsxPK)gInmGGRbowtie/templates/datagrid.jsxPK0\pI nbllI[bowtie/templates/dropdown.jsxPK)gI1=9 9 abowtie/templates/fixedtable.jsxPK0\pIA##flbowtie/templates/griddle.jsxPK)gIEErbowtie/templates/index.htmlPK0I'Atbowtie/templates/index.jsxPK0\pI-F`HHHh{bowtie/templates/nouislider.jsxPK0\pIsoPQQbowtie/templates/plotly.jsxPK0\pIm wbowtie/templates/server.pyPK)gIy"{bowtie/templates/webpack.config.jsPK]=Ibowtie/tests/__init__.pyPK0\pIνҶbowtie/tests/test_compile.pyPKoIW*((큼bowtie/tests/test_plotly.pyPK!H|&Ubbowtie-0.0.11.dist-info/WHEELPK!H՝j bowtie-0.0.11.dist-info/METADATAPK!Hf]=,bowtie-0.0.11.dist-info/RECORDPKp