<commit_before>/*

EAGLE .BRD Renderer

Benjamin D. Richards, Gamelab

This script generates a model of a PCB (printed circuit board)
based on XML data in a BRD file. Cadsoft EAGLE BRD files use XML from version
6.0 onwards.

The model includes `Connectors` with which the PCB may be added
as a component to a larger hardware system.

This visualisation should be regarded as indicative only.
Although every care has been taken to represent the actual data,
it is not guaranteed to be completely accurate.

*/

/**
@module EagleBrdRenderer
**/

var EagleBrdRenderer = function( xml, params ) {

	/**
	Creator of board renders. Reads XML and composites the appropriate
	textures and geometry.

	@class EagleBrdRenderer
	@constructor
	@param xml {Document} XML document to parse
	@param [params] {object} Composite parameter object
		@param [params.pixelMicrons=35] {number} Resolution of texture maps.
			By default, this is 35 microns, equal to the thickness
			of a default copper layer.
	**/

	/**
	Optional parameters used to configure render.

	@property params {object}
	**/
	this.params = params;

	/**
	XML document that contains the board data

	@property xml {Document}
	**/
	this.xml = xml;

	/**
	PCB layers to be combined into the final board

	@property layers {array}
	@default []
	**/
	this.layers = [];

	this._setDefaultParams();

	this._parseDesignRules();

	this._parseBoardBounds();

	// TODO Init layers
	// We probably want a proper layer manager here, rather than
	// a naïve array. On the other hand, the BRD does specify the complete
	// layer list.  On the first hand again, most of them are unused.

	// TODO Parse BRD into layers
};

EagleBrdRenderer.prototype._getChordPoints = function( wire ) {

	/**
	Return a list of `[ x, y ]` coordinate pairs
	representing the cardinal points of a circle described by the
	curvature of the wire parameter. This list may be length 0.

	@method _getChordPoints
	@return array
	@private
	**/

	var ang, bearing, centroidX, centroidY, dx, dy, len, radius,
		sweepMin, sweepMax,
		x1, x2, y1, y2,
		curve = parseFloat( wire.getAttribute( "curve" ) ) * Math.PI / 180,
		points = [];

	// Zero-curvature points are straight. Obviously.
	if ( curve === 0 ) {
		return points;
	}

	// Get coords.
	x1 = parseFloat( wire.getAttribute( "x1" ) );
	y1 = parseFloat( wire.getAttribute( "y1" ) );
	x2 = parseFloat( wire.getAttribute( "x2" ) );
	y2 = parseFloat( wire.getAttribute( "y2" ) );
	dx = x2 - x1;
	dy = y2 - y1;
	len = Math.sqrt( Math.pow( dx, 2 ) + Math.pow( dy, 2 ) );
	bearing = Math.atan2( dy, dx );

	// Determine circle characteristics.
	// Per chord identities, chord length = 2 * radius * sin( angle / 2 )
	radius = len / ( 2 * Math.sin( curve / 2 ) );

	// Determine sidedness
	if ( curve > 0 ) {
		ang = bearing + Math.PI / 2 - curve / 2;
	} else {
		ang = bearing - Math.PI / 2 + curve / 2;
	}
	centroidX = x1 + radius * Math.cos( ang );
	centroidY = y1 + radius * Math.sin( ang );

	// Determine angular sweep from point 1 to point 2.
	// The sweep may be greater than PI.
	sweepMin = Math.atan2( centroidY - y1, centroidX - x1 );
	sweepMax = Math.atan2( centroidY - y2, centroidX - x2 );
	if ( sweepMin > sweepMax ) {
		sweepMax += Math.PI * 2;
	}

	// Determine points that fall within sweep.
	// Must check extra points, as `sweepMax` may be as high as PI * 3.
	// Note that perfect matches are discarded,
	// as this indicates the wire point is on that cardinal point.
	if ( sweepMin < -Math.PI / 2 && sweepMax > -Math.PI / 2 ) {
		points.push( [ centroidX, centroidY + radius ] );
	}
	if ( sweepMin < 0 && sweepMax > 0 ) {
		points.push( [ centroidX - radius, centroidY ] );
	}
	if ( sweepMin < Math.PI / 2 && sweepMax > Math.PI / 2 ) {
		points.push( [ centroidX, centroidY - radius ] );
	}
	if ( sweepMin < Math.PI && sweepMax > Math.PI ) {
		points.push( [ centroidX + radius, centroidY ] );
	}
	if ( sweepMax > Math.PI * 1.5 ) {
		points.push( [ centroidX, centroidY + radius ] );
	}
	if ( sweepMax > Math.PI * 2 ) {
		points.push( [ centroidX - radius, centroidY ] );
	}
	if ( sweepMax > Math.PI * 2.5 ) {
		points.push( [ centroidX, centroidY - radius ] );
	}
	if ( sweepMax > Math.PI * 3 ) {
		points.push( [ centroidX + radius, centroidY ] );
	}

	return points;
};

EagleBrdRenderer.prototype._parseDesignRules = function() {

	/**
	Set `designRules` to contain important circuit rules from XML.

	@method _parseDesignRules
	@private
	**/

	var i,
		rules = this.xml.getElementsByTagName( "designrules" )[ 0 ]
			.getElementsByTagName( "param" );

	/**
	Design rules for this PCB layout. These are used in determining
	various important layout characteristics.

	@property designRules {object}
	@default {}
	**/
	this.designRules = {};

	for ( i = 0; i < rules.length; i++ ) {
		this.designRules[ rules[ i ].getAttribute( "name" ) ] =
			rules[ i ].getAttribute( "value" );
	}
};

EagleBrdRenderer.prototype._parseBoardBounds = function() {

	/**
	Determine and record the boundaries of the board surface.
	This is derived from `eagle.drawing.board.plain` wires.

	@method _parseBoardBounds
	@private
	**/

	var curve, chordPoints, i, j, k, x, y,
		testMinMax = function( x, y ) {
			minX = Math.min( x, minX );
			minY = Math.min( y, minY );
			maxX = Math.max( x, maxX );
			maxY = Math.max( y, maxY );
		},
		maxX = 0,
		maxY = 0,
		minX = 0,
		minY = 0,
		wires = this.xml.getElementsByTagName( "plain" )[ 0 ]
			.getElementsByTagName( "wire" );

	/*
	Some wires may be curved. Under rare circumstances,
	such as a curved line between two horizontally-aligned points,
	this might disrupt normal minmax point estimates of bounds.

	Solution: chord checking. This is simple and robust.
	Wire curve is a circular chord. If the chord sweep takes in
	any cardinal direction (top, bottom, left, right), that cardinal point
	is a minmax candidate. As we know the start and end points of the chord,
	we can compute this easily.
	*/

	// Assemble boundaries
	for ( i = 0; i < wires.length; i++ ) {

		// Simple minmax point test
		for ( j = 1; j <= 2; j++ ) {
			x = wires[ i ].getAttribute( "x" + j );
			y = wires[ i ].getAttribute( "y" + j );
			testMinMax( x, y );
		}

		// Cord checks
		curve = wires[ i ].getAttribute( "curve" );
		if ( curve ) {
			chordPoints = this._getChordPoints( wires[ i ] );
			for ( k = 0; k < chordPoints.length; k++ ) {
				testMinMax( chordPoints[ k ][ 0 ], chordPoints[ k ][ 1 ] );
			}
		}
	}

	/**
	Information on physical bounds of board, in mm.

	@property bounds {object}
		@property bounds.maxX {number}
		@property bounds.maxY {number}
		@property bounds.minX {number}
		@property bounds.minY {number}
		@property bounds.width {number}
		@property bounds.height {number}
	**/
	this.bounds = {
		minX: minX,
		minY: minY,
		maxX: maxX,
		maxY: maxY,
		width: maxX - minX,
		height: maxY - minY
	};

	/**
	Horizontal resolution of this board's texture maps.

	@property width {number}
	**/
	this.width = Math.ceil( this.bounds.width * this.params.pixelMicrons );

	/**
	Vertical resolution of this board's texture maps.

	@property height {number}
	**/
	this.height = Math.ceil( this.bounds.height * this.params.pixelMicrons );

	// TODO slots

	// TODO draw outline mask
};

EagleBrdRenderer.prototype._setDefaultParams = function() {

	/**
	Ensure that required params are set.

	@method _setDefaultParams
	@private
	**/

	if ( !this.params ) {
		this.params = {};
	}

	this.params.pixelMicrons = this.params.pixelMicrons || 35;
};

EagleBrdRenderer.ChordData = function( wire ) {

	/**
	Analysis of the chord and circular segment described
	by a curving wire element.

	@namespace EagleBrdRenderer
	@class ChordData
	@constructor
	@param wire {Element} EAGLE BRD `wire` XML element
	**/

	var ang;

	/**
	Curvature of wire segment

	@property curve {number}
	**/
	this.curve = parseFloat( wire.getAttribute( "curve" ) ) * Math.PI / 180;

	/**
	Horizontal coodinate of wire start

	@property x1 {number}
	**/
	this.x1 = parseFloat( wire.getAttribute( "x1" ) );

	/**
	Vertical coodinate of wire start

	@property y1 {number}
	**/
	this.y1 = parseFloat( wire.getAttribute( "y1" ) );

	/**
	Horizontal coodinate of wire end

	@property x2 {number}
	**/
	this.x2 = parseFloat( wire.getAttribute( "x2" ) );

	/**
	Vertical coodinate of wire end

	@property y2 {number}
	**/
	this.y2 = parseFloat( wire.getAttribute( "y2" ) );

	/**
	Horizontal wire displacement

	@property dx {number}
	**/
	this.dx = this.x2 - this.x1;

	/**
	Vertical wire displacement

	@property dy {number}
	**/
	this.dy = this.y2 - this.y1;

	/**
	Distance between start and end points of wire.

	@property chord {number}
	**/
	this.chord = Math.sqrt( Math.pow( this.dx, 2 ) + Math.pow( this.dy, 2 ) );

	/**
	Angle from start to end points of wire.

	@property bearing {number}
	**/
	this.bearing = Math.atan2( this.dy, this.dx );

	/**
	Radius of chord arc. Per chord identity,
	chord = 2 * radius * sin( angle / 2 ), rearranged to acquire radius.

	@property radius {number}
	**/
	this.radius = this.chord / ( 2 * Math.sin( this.curve / 2 ) );

	// Determine sidedness
	ang = curve > 0 ?
		bearing + Math.PI / 2 - curve / 2 :
		bearing - Math.PI / 2 + curve / 2;

	/**
	Horizontal position of arc origin

	@property x {number}
	**/
	this.x = this.x1 + radius * Math.cos( ang );

	/**
	Vertical position of arc origin

	@property y {number}
	**/
	this.y = this.y1 + radius * Math.sin( ang );
};
<commit_msg>Add canvas creation to bounds parser. Deprivatise `getChordPoints`.<commit_after>/*

EAGLE .BRD Renderer

Benjamin D. Richards, Gamelab

This script generates a model of a PCB (printed circuit board)
based on XML data in a BRD file. Cadsoft EAGLE BRD files use XML from version
6.0 onwards.

The model includes `Connectors` with which the PCB may be added
as a component to a larger hardware system.

This visualisation should be regarded as indicative only.
Although every care has been taken to represent the actual data,
it is not guaranteed to be completely accurate.

*/

/**
@module EagleBrdRenderer
**/

var EagleBrdRenderer = function( xml, params ) {

	/**
	Creator of board renders. Reads XML and composites the appropriate
	textures and geometry.

	@class EagleBrdRenderer
	@constructor
	@param xml {Document} XML document to parse
	@param [params] {object} Composite parameter object
		@param [params.pixelMicrons=35] {number} Resolution of texture maps.
			By default, this is 35 microns, equal to the thickness
			of a default copper layer.
	**/

	/**
	Optional parameters used to configure render.

	@property params {object}
	**/
	this.params = params;

	/**
	XML document that contains the board data

	@property xml {Document}
	**/
	this.xml = xml;

	/**
	Named textures. Includes layer textures and other data.

	@property buffers {object}
	@default {}
	**/
	this.buffers = {};

	/**
	PCB layers to be combined into the final board

	@property layers {array}
	@default []
	**/
	this.layers = [];

	this._setDefaultParams();

	this._parseDesignRules();

	this._parseBoardBounds();

	// TODO Init layers
	// We probably want a proper layer manager here, rather than
	// a naïve array. On the other hand, the BRD does specify the complete
	// layer list.  On the first hand again, most of them are unused.

	// TODO Parse BRD into layers
};

EagleBrdRenderer.prototype.getChordPoints = function( wire ) {

	/**
	Return a list of `[ x, y ]` coordinate pairs
	representing the cardinal points of a circle described by the
	curvature of the wire parameter. This list may be length 0.

	@method getChordPoints
	@return array
	**/

	var ang, bearing, centroidX, centroidY, dx, dy, len, radius,
		sweepMin, sweepMax,
		x1, x2, y1, y2,
		curve = parseFloat( wire.getAttribute( "curve" ) ) * Math.PI / 180,
		points = [];

	// Zero-curvature points are straight. Obviously.
	if ( curve === 0 ) {
		return points;
	}

	// Get coords.
	x1 = parseFloat( wire.getAttribute( "x1" ) );
	y1 = parseFloat( wire.getAttribute( "y1" ) );
	x2 = parseFloat( wire.getAttribute( "x2" ) );
	y2 = parseFloat( wire.getAttribute( "y2" ) );
	dx = x2 - x1;
	dy = y2 - y1;
	len = Math.sqrt( Math.pow( dx, 2 ) + Math.pow( dy, 2 ) );
	bearing = Math.atan2( dy, dx );

	// Determine circle characteristics.
	// Per chord identities, chord length = 2 * radius * sin( angle / 2 )
	radius = len / ( 2 * Math.sin( curve / 2 ) );

	// Determine sidedness
	if ( curve > 0 ) {
		ang = bearing + Math.PI / 2 - curve / 2;
	} else {
		ang = bearing - Math.PI / 2 + curve / 2;
	}
	centroidX = x1 + radius * Math.cos( ang );
	centroidY = y1 + radius * Math.sin( ang );

	// Determine angular sweep from point 1 to point 2.
	// The sweep may be greater than PI.
	sweepMin = Math.atan2( centroidY - y1, centroidX - x1 );
	sweepMax = Math.atan2( centroidY - y2, centroidX - x2 );
	if ( sweepMin > sweepMax ) {
		sweepMax += Math.PI * 2;
	}

	// Determine points that fall within sweep.
	// Must check extra points, as `sweepMax` may be as high as PI * 3.
	// Note that perfect matches are discarded,
	// as this indicates the wire point is on that cardinal point.
	if ( sweepMin < -Math.PI / 2 && sweepMax > -Math.PI / 2 ) {
		points.push( [ centroidX, centroidY + radius ] );
	}
	if ( sweepMin < 0 && sweepMax > 0 ) {
		points.push( [ centroidX - radius, centroidY ] );
	}
	if ( sweepMin < Math.PI / 2 && sweepMax > Math.PI / 2 ) {
		points.push( [ centroidX, centroidY - radius ] );
	}
	if ( sweepMin < Math.PI && sweepMax > Math.PI ) {
		points.push( [ centroidX + radius, centroidY ] );
	}
	if ( sweepMax > Math.PI * 1.5 ) {
		points.push( [ centroidX, centroidY + radius ] );
	}
	if ( sweepMax > Math.PI * 2 ) {
		points.push( [ centroidX - radius, centroidY ] );
	}
	if ( sweepMax > Math.PI * 2.5 ) {
		points.push( [ centroidX, centroidY - radius ] );
	}
	if ( sweepMax > Math.PI * 3 ) {
		points.push( [ centroidX + radius, centroidY ] );
	}

	return points;
};

EagleBrdRenderer.prototype._parseDesignRules = function() {

	/**
	Set `designRules` to contain important circuit rules from XML.

	@method _parseDesignRules
	@private
	**/

	var i,
		rules = this.xml.getElementsByTagName( "designrules" )[ 0 ]
			.getElementsByTagName( "param" );

	/**
	Design rules for this PCB layout. These are used in determining
	various important layout characteristics.

	@property designRules {object}
	@default {}
	**/
	this.designRules = {};

	for ( i = 0; i < rules.length; i++ ) {
		this.designRules[ rules[ i ].getAttribute( "name" ) ] =
			rules[ i ].getAttribute( "value" );
	}
};

EagleBrdRenderer.prototype._parseBoardBounds = function() {

	/**
	Determine and record the boundaries of the board surface.
	This is derived from `eagle.drawing.board.plain` wires.

	@method _parseBoardBounds
	@private
	**/

	var buffer, ctx, curve, chordPoints, i, j, k, x, y,
		testMinMax = function( x, y ) {
			minX = Math.min( x, minX );
			minY = Math.min( y, minY );
			maxX = Math.max( x, maxX );
			maxY = Math.max( y, maxY );
		},
		maxX = 0,
		maxY = 0,
		minX = 0,
		minY = 0,
		wires = this.xml.getElementsByTagName( "plain" )[ 0 ]
			.getElementsByTagName( "wire" );

	// TODO Get wires by layer, rather than by element `plain`

	/*
	Some wires may be curved. Under rare circumstances,
	such as a curved line between two horizontally-aligned points,
	this might disrupt normal minmax point estimates of bounds.

	Solution: chord checking. This is simple and robust.
	Wire curve is a circular chord. If the chord sweep takes in
	any cardinal direction (top, bottom, left, right), that cardinal point
	is a minmax candidate. As we know the start and end points of the chord,
	we can compute this easily.
	*/

	// Assemble boundaries
	for ( i = 0; i < wires.length; i++ ) {

		// Simple minmax point test
		for ( j = 1; j <= 2; j++ ) {
			x = wires[ i ].getAttribute( "x" + j );
			y = wires[ i ].getAttribute( "y" + j );
			testMinMax( x, y );
		}

		// Cord checks
		curve = wires[ i ].getAttribute( "curve" );
		if ( curve ) {
			chordPoints = this.getChordPoints( wires[ i ] );
			for ( k = 0; k < chordPoints.length; k++ ) {
				testMinMax( chordPoints[ k ][ 0 ], chordPoints[ k ][ 1 ] );
			}
		}
	}

	/**
	Information on physical bounds of board, in mm.

	@property bounds {object}
		@property bounds.maxX {number}
		@property bounds.maxY {number}
		@property bounds.minX {number}
		@property bounds.minY {number}
		@property bounds.width {number}
		@property bounds.height {number}
	**/
	this.bounds = {
		minX: minX,
		minY: minY,
		maxX: maxX,
		maxY: maxY,
		width: maxX - minX,
		height: maxY - minY
	};

	/**
	Horizontal resolution of this board's texture maps.

	@property width {number}
	**/
	this.width = Math.ceil( this.bounds.width * this.params.pixelMicrons );

	/**
	Vertical resolution of this board's texture maps.

	@property height {number}
	**/
	this.height = Math.ceil( this.bounds.height * this.params.pixelMicrons );

	// TODO slots

	if ( !( this.width > 1 && this.height > 1 ) ) {
		console.error( "Error: Texture dimensions too small.",
			"BRD boundaries not found, or too many microns per pixel." );
		return;
	}

	// Draw outline mask
	buffer = document.createElement( "canvas" );
	buffer.width = this.width;
	buffer.height = this.height;
	this.buffers.bounds = buffer;

	ctx = buffer.getContext( "2d" );

	ctx.fillStyle = "rgba( 0.1, 0.8, 0.1, 0.5 )";
	ctx.beginPath();
	ctx.fill();
};

EagleBrdRenderer.prototype._setDefaultParams = function() {

	/**
	Ensure that required params are set.

	@method _setDefaultParams
	@private
	**/

	if ( !this.params ) {
		this.params = {};
	}

	this.params.pixelMicrons = this.params.pixelMicrons || 35;
};

EagleBrdRenderer.ChordData = function( wire ) {

	/**
	Analysis of the chord and circular segment described
	by a curving wire element.

	@namespace EagleBrdRenderer
	@class ChordData
	@constructor
	@param wire {Element} EAGLE BRD `wire` XML element
	**/

	var ang;

	/**
	Curvature of wire segment

	@property curve {number}
	**/
	this.curve = parseFloat( wire.getAttribute( "curve" ) ) * Math.PI / 180;

	/**
	Horizontal coodinate of wire start

	@property x1 {number}
	**/
	this.x1 = parseFloat( wire.getAttribute( "x1" ) );

	/**
	Vertical coodinate of wire start

	@property y1 {number}
	**/
	this.y1 = parseFloat( wire.getAttribute( "y1" ) );

	/**
	Horizontal coodinate of wire end

	@property x2 {number}
	**/
	this.x2 = parseFloat( wire.getAttribute( "x2" ) );

	/**
	Vertical coodinate of wire end

	@property y2 {number}
	**/
	this.y2 = parseFloat( wire.getAttribute( "y2" ) );

	/**
	Horizontal wire displacement

	@property dx {number}
	**/
	this.dx = this.x2 - this.x1;

	/**
	Vertical wire displacement

	@property dy {number}
	**/
	this.dy = this.y2 - this.y1;

	/**
	Distance between start and end points of wire.

	@property chord {number}
	**/
	this.chord = Math.sqrt( Math.pow( this.dx, 2 ) + Math.pow( this.dy, 2 ) );

	/**
	Angle from start to end points of wire.

	@property bearing {number}
	**/
	this.bearing = Math.atan2( this.dy, this.dx );

	/**
	Radius of chord arc. Per chord identity,
	chord = 2 * radius * sin( angle / 2 ), rearranged to acquire radius.

	@property radius {number}
	**/
	this.radius = this.chord / ( 2 * Math.sin( this.curve / 2 ) );

	// Determine sidedness
	ang = curve > 0 ?
		bearing + Math.PI / 2 - curve / 2 :
		bearing - Math.PI / 2 + curve / 2;

	/**
	Horizontal position of arc origin

	@property x {number}
	**/
	this.x = this.x1 + radius * Math.cos( ang );

	/**
	Vertical position of arc origin

	@property y {number}
	**/
	this.y = this.y1 + radius * Math.sin( ang );
};
<|endoftext|>


<commit_before>const { client: Client, connection: Connection } = require('websocket');
const { EventEmitter } = require('events');
const wait = require('./wait');

class ReconnectingWebSocketNode extends EventEmitter {
	/** @param {Connection} connection */
	constructor(connection, connectParams = {}, options) {
		super();
		this.connectParams = connectParams;
		this.needReconnect = true;
		this._updateConnection(connection);
	}

	static async connect(requestUrl, protocols, origin, headers, options) {
		const connection = await ReconnectingWebSocketNode._connect(requestUrl, protocols, origin, headers, options);
		return new ReconnectingWebSocketNode(connection, { requestUrl, protocols, origin, headers, options }, options);
	}

	static _connect(requestUrl, protocols, origin, headers, options) {
		return new Promise((resolve, reject) => {
			const client = new Client();
			client.on('connect', (connection) => {
				resolve(connection);
			});
			client.on('connectFailed', (err) => {
				reject(err);
			});
			client.connect(requestUrl, protocols, origin, headers, options);
		});
	}

	_updateConnection(connection) {
		const reconnect = async () => {
			await wait(5000);

			const { requestUrl, protocols, origin, headers, options } = this.connectParams;
			try {
				// コネクションの置き換え
				const connection = await ReconnectingWebSocketNode._connect(requestUrl, protocols, origin, headers, options);
				this._updateConnection(connection);
				return true;
			}
			catch (err) {
				return false;
			}
		};

		this._linkConnection(connection);
		connection.on('close', async () => {
			while (this.needReconnect) {
				console.log('reconnecting...');
				if (await reconnect()) {
					console.log('reconnected!');
					break;
				}
			}
		});
		this.connection = connection;
	}

	_linkConnection(connection) {
		const linkEvent = (name) => {
			connection.on(name, (...args) => this.emit(name, ...args));
		};
		const events = ['message', 'frame', 'close', 'error', 'ping', 'pong'];
		for (const event of events) {
			linkEvent(event);
		}

		const linkMethod = name => {
			this[name] = (...args) => connection[name](...args);
		};
		const methods = ['drop', 'sendUTF', 'sendBytes', 'send', 'ping', 'pong', 'sendFrame'];
		for (const method of methods) {
			linkMethod(method);
		}
		this.close = (...args) => {
			this.needReconnect = false;
			connection.close(...args);
		};
	}
}
module.exports = ReconnectingWebSocketNode;
<commit_msg>reconnection time that gradually becomes longer<commit_after>const { client: Client, connection: Connection } = require('websocket');
const { EventEmitter } = require('events');
const wait = require('./wait');

class ReconnectingWebSocketNode extends EventEmitter {
	/** @param {Connection} connection */
	constructor(connection, connectParams = {}, options) {
		super();
		this.connectParams = connectParams;
		this.needReconnect = true;
		this.retryCount = 0;
		this._updateConnection(connection);
	}

	static async connect(requestUrl, protocols, origin, headers, options) {
		const connection = await ReconnectingWebSocketNode._connect(requestUrl, protocols, origin, headers, options);
		return new ReconnectingWebSocketNode(connection, { requestUrl, protocols, origin, headers, options }, options);
	}

	static _connect(requestUrl, protocols, origin, headers, options) {
		return new Promise((resolve, reject) => {
			const client = new Client();
			client.on('connect', (connection) => {
				resolve(connection);
			});
			client.on('connectFailed', (err) => {
				reject(err);
			});
			client.connect(requestUrl, protocols, origin, headers, options);
		});
	}

	_updateConnection(connection) {
		const reconnect = async () => {
			await wait((5 + 5 * this.retryCount) * 1000);

			const { requestUrl, protocols, origin, headers, options } = this.connectParams;
			try {
				// コネクションの置き換え
				const connection = await ReconnectingWebSocketNode._connect(requestUrl, protocols, origin, headers, options);
				this._updateConnection(connection);
				return true;
			}
			catch (err) {
				return false;
			}
		};

		this._linkConnection(connection);
		connection.on('close', async () => {
			while (this.needReconnect) {
				console.log('reconnecting...');
				if (await reconnect()) {
					console.log('reconnected!');
					this.retryCount = 0;
					break;
				}
				this.retryCount++;
			}
		});
		this.connection = connection;
	}

	_linkConnection(connection) {
		const linkEvent = (name) => {
			connection.on(name, (...args) => this.emit(name, ...args));
		};
		const events = ['message', 'frame', 'close', 'error', 'ping', 'pong'];
		for (const event of events) {
			linkEvent(event);
		}

		const linkMethod = name => {
			this[name] = (...args) => connection[name](...args);
		};
		const methods = ['drop', 'sendUTF', 'sendBytes', 'send', 'ping', 'pong', 'sendFrame'];
		for (const method of methods) {
			linkMethod(method);
		}
		this.close = (...args) => {
			this.needReconnect = false;
			connection.close(...args);
		};
	}
}
module.exports = ReconnectingWebSocketNode;
<|endoftext|>


<commit_before>/**
 * Photo class - specific Roi type class for rendering simple photo.
 * @constructor
 * @final
 * @extends {Melown.Roi}
 */
Melown.Roi.Photo = function(config_, core_, options_) {
    this.cubeTree_ = [];

    // config properties
    this.photoOrientation_ = null;
    this.imageSize_ = null;
    this.imageUrl_ = null;

    // claculated config properties
    this.photoMatrices_ = [];
    this.orientationMatrix_ = null;

    this.super_ = Melown.Roi.prototype;
    Melown.Roi.call(this, config_, core_, options_);
}

// Inheritance from Roi
Melown.Roi.Photo.prototype = Object.create(Melown.Roi.prototype);
Melown.Roi.Photo.prototype.constructor = Melown.Roi.Photo;

// Register class to Roi type dictionary (used by Roi.Fetch function)
Melown.Roi.Type['photo'] = Melown.Roi.Photo;

// Protected methods

Melown.Roi.Photo.prototype._init = function() {
    // check browser instance
    // prepare UI

    this.super_._init.call(this);
}

Melown.Roi.Photo.prototype._processConfig = function() {
    this.super_._processConfig.call(this);

    if (this.state_ === Melown.Roi.State.Error) {
        return;
    }

    var err = null;
    if (typeof this.config_['photo'] !== 'object'
        || this.config_['photo'] === null) {
        err = new Error('Missing (or type error) photo key in config JSON');
    } else if (!this.config_['photo']['orientation'] instanceof Array
        || this.config_['photo']['orientation'].length !== 3) {
        err = new Error('Missing (or type error) photo.orientation in config JSON');
    } else if (!this.config_['photo']['imageSize'] instanceof Array
        || this.config_['photo']['imageSize'].length !== 2) {
        err = new Error('Missing (or type error) photo.imageSize in config JSON');
    } else if (typeof this.config_['photo']['imageUrl'] !== 'string'
        ) { //|| !Melown.Utils.urlSanity(this.config_['photo']['imageUrl'])) {
        err = new Error('Missing (or type error) photo.imageUrl in config JSON');
    }

    if (err) {
        this.state_ = Melown.Roi.State.Error;
        console.error(err);
    } else {
        this.photoOrientation_ = this.config_['photo']['orientation'];
        this.imageSize_ = this.config_['photo']['imageSize'][0];
        this.tileTemplate_ = this.config_['photo']['imageUrl'];
    }
}

Melown.Roi.Photo.prototype._initFinalize = function() {
    this.super_._initFinalize.call(this);

    // orient photo
    this.orientationMatrix_ = Melown.mat4.create();
    var rotateZ = Melown.rotationMatrix(2, Melown.radians(-this.photoOrientation_[2]));
    var rotateY = Melown.rotationMatrix(1, Melown.radians(-this.photoOrientation_[1]));
    var rotateX = Melown.rotationMatrix(0, Melown.radians(-this.photoOrientation_[0]));
    Melown.mat4.multiply(this.orientationMatrix_, rotateZ, this.orientationMatrix_);
    Melown.mat4.multiply(this.orientationMatrix_, rotateY, this.orientationMatrix_);
    Melown.mat4.multiply(this.orientationMatrix_, rotateX, this.orientationMatrix_);
}

Melown.Roi.Photo.prototype._tick = function() {
    this.super_._tick.call(this);

}

Melown.Roi.Photo.prototype._update = function() {
    this.super_._update.call(this);

    // TODO

    // set draw dirty flag (cube will be redraw in next tick)
    this.needsRedraw_ = true;
}

Melown.Roi.Photo.prototype._draw = function() {

    if (!this.map_) {
        return;
    }

    // TODO
}
<commit_msg>Photo ROI finalized<commit_after>/**
 * Photo class - specific Roi type class for rendering simple photo.
 * @constructor
 * @final
 * @extends {Melown.Roi}
 */
Melown.Roi.Photo = function(config_, core_, options_) {
    this.cubeTree_ = [];

    // config properties
    this.photoOrientation_ = null;
    this.imageSize_ = null;
    this.imageUrl_ = null;

    // claculated config properties
    this.photoMatrices_ = [];
    this.orientationMatrix_ = null;
    this.texture_ = null;
    this.image_ = null;

    this.super_ = Melown.Roi.prototype;
    Melown.Roi.call(this, config_, core_, options_);
}

// Inheritance from Roi
Melown.Roi.Photo.prototype = Object.create(Melown.Roi.prototype);
Melown.Roi.Photo.prototype.constructor = Melown.Roi.Photo;

// Register class to Roi type dictionary (used by Roi.Fetch function)
Melown.Roi.Type['photo'] = Melown.Roi.Photo;

// Protected methods

Melown.Roi.Photo.prototype._init = function() {
    // check browser instance
    // prepare UI

    this.super_._init.call(this);
}

Melown.Roi.Photo.prototype._processConfig = function() {
    this.super_._processConfig.call(this);

    if (this.state_ === Melown.Roi.State.Error) {
        return;
    }

    var err = null;
    if (typeof this.config_['photo'] !== 'object'
        || this.config_['photo'] === null) {
        err = new Error('Missing (or type error) photo key in config JSON');
    } else if (!this.config_['photo']['orientation'] instanceof Array
        || this.config_['photo']['orientation'].length !== 3) {
        err = new Error('Missing (or type error) photo.orientation in config JSON');
    } else if (!this.config_['photo']['imageSize'] instanceof Array
        || this.config_['photo']['imageSize'].length !== 2) {
        err = new Error('Missing (or type error) photo.imageSize in config JSON');
    } else if (typeof this.config_['photo']['imageUrl'] !== 'string'
        ) { //|| !Melown.Utils.urlSanity(this.config_['photo']['imageUrl'])) {
        err = new Error('Missing (or type error) photo.imageUrl in config JSON');
    }

    if (err) {
        this.state_ = Melown.Roi.State.Error;
        console.error(err);
    } else {
        this.photoOrientation_ = this.config_['photo']['orientation'];
        this.imageSize_ = this.config_['photo']['imageSize'];
        this.imageUrl_ = this.config_['photo']['imageUrl'];
    }
}

Melown.Roi.Photo.prototype._initFinalize = function() {
    this.super_._initFinalize.call(this);

    // load texture
    this.image_ = Melown.Http.imageFactory(this.imageUrl_, function(data_) {
        this.texture_ = this.renderer_.createTexture({source : this.image_})
        this.image_ = null;
    }.bind(this), function(err_) {
        this.image_ = null;
        this.state_ = Melown.Roi.State.Error;
        console.error(err_);
    }.bind(this));

    // orient photo
    this.orientationMatrix_ = Melown.rotationMatrix(2, Melown.radians(-this.photoOrientation_[2]));
    var rotateY = Melown.rotationMatrix(1, Melown.radians(-this.photoOrientation_[1]));
    var rotateX = Melown.rotationMatrix(0, Melown.radians(-this.photoOrientation_[0]));
    Melown.mat4.multiply(this.orientationMatrix_, rotateY, this.orientationMatrix_);
    Melown.mat4.multiply(this.orientationMatrix_, rotateX, this.orientationMatrix_);
}

Melown.Roi.Photo.prototype._tick = function() {
    this.super_._tick.call(this);

}

Melown.Roi.Photo.prototype._update = function() {
    this.super_._update.call(this);

    // TODO

    // set draw dirty flag (cube will be redraw in next tick)
    this.needsRedraw_ = true;
}

Melown.Roi.Photo.prototype._draw = function() {
    if (this.state_ !== Melown.Roi.State.FadingIn
        && this.state_ !== Melown.Roi.State.FadingOut
        && this.state_ !== Melown.Roi.State.Presenting) {
        return;
    }

    if (!this.map_ || !this.texture_) {
        return;
    }

    //  projection-view matrix from map.getCamera()
    var cam_ = this.map_.getCameraInfo();
    var pv_ = cam_['view-projection-matrix'];

    var mvp_ = Melown.mat4.create();
    Melown.mat4.identity(mvp_);

    var trn_ = Melown.translationMatrix(-0.5, -0.5, 0);
    Melown.mat4.multiply(trn_, mvp_, mvp_);

    var rot_ = Melown.rotationMatrix(0, Melown.radians(180));
    Melown.mat4.multiply(rot_, mvp_, mvp_);

    var w = window;
    var d = document;
    var e = d.documentElement;
    var g = d.getElementsByTagName('body')[0];
    var x = w.innerWidth || e.clientWidth || g.clientWidth;
    var y = w.innerHeight|| e.clientHeight|| g.clientHeight;

    var sclFacs = [2.2, 2.2];
    sclFacs[0] = sclFacs[1] * (y/x);
    if (this.imageSize_[0] > this.imageSize_[1]) {
        sclFacs[0] = sclFacs[0]*(this.imageSize_[0]/this.imageSize_[1]);
    } else {
        sclFacs[1] = sclFacs[1]*(this.imageSize_[1]/this.imageSize_[0]);
    }
    var scl_ = Melown.scaleMatrix(sclFacs[0],sclFacs[1],1);
    //Melown.mat4.multiply(tile_.mat_, mvp_, mvp_);
    Melown.mat4.multiply(scl_, mvp_, mvp_);

    // draw tile
    opts_ = {};
    opts_["mvp"] = mvp_;
    opts_["texture"] = this.texture_;
    opts_["color"] = [255, 255, 255, this.alpha_*255];
    opts_["blend"] = (this.alpha_ < 1.0);
    this.renderer_.drawBillboard(opts_);
}
<|endoftext|>


<commit_before>'use strict';

import 'babel-polyfill';
import {servers} from '../defaultServers';
import Rx from '../rx';
var {ajax, create, defer, empty, from, fromEvent, of} = Rx.Observable;
var EMPTY = empty();
var {asap} = Rx.Scheduler;
import getErrors from '../import/errorChecking';
import infer from '../import/infer.js';
import {dataSubType, FILE_FORMAT} from '../import/constants';
const {GENOMIC_MATRIX, CLINICAL_MATRIX} = FILE_FORMAT;
import {Let, assocIn, copyStr, getIn, has, iterable} from '../underscore_ext';
import backPressure from '../import/backPressure';
import chunkReader from '../import/chunkReader';
require('./html5-formdata-polyfill'); // for safari

const referenceHost = 'https://reference.xenahubs.net';

const getBaseProbemapName = name => name ? name.replace(/(.*[/])/, '') : null;

const createMetaDataFile = (state) => {
    const probemap = getBaseProbemapName(getIn(state, ['probemap', 'name']));
    return JSON.stringify({
        cohort: state[state.cohortRadio],
        dataSubType: dataSubType[state.dataType],
        type: state.fileFormat,
        assembly: state.assembly || void 0,
        probemap: probemap ? probemap : void 0
    }, null, 4);
};

const postFile = (file) => {
    const payload = {
        url: `${servers.localHub}/upload/`,
        body: file,
        responseType: 'text',
        method: 'POST',
        crossDomain: true
    };

    return ajax(payload).map(r => r.response);
};

const updateFile = (fileName) => {
    const payload = {
        url: `${servers.localHub}/update/`,
        body: {'file': fileName},
        responseType: 'text',
        method: 'POST',
        crossDomain: true
    };
    return ajax(payload).map(r => JSON.parse(r.response));
};

const readFileObs = fileHandle => {
    return create(obs => {
		var reading = true;
        const reader = new FileReader();
        reader.onload = (e) => {
			reading = false;
            obs.next(e.target.result);
            obs.complete();
        };
        reader.onerror = ev => obs.error(ev.target.error);
        reader.readAsBinaryString(fileHandle);
        return () => reading && reader.abort();
    });
};

const matchFormat = fileInfo => {
	const {form: {fileFormat, dataType}} = fileInfo;
	return fileFormat === GENOMIC_MATRIX && dataType === 'phenotype' ?
			assocIn(fileInfo,
					['form', 'fileFormat'], CLINICAL_MATRIX,
					['isTransposed'], true) :
		fileFormat === CLINICAL_MATRIX && dataType !== 'phenotype' ?
			assocIn(fileInfo,
					['form', 'fileFormat'], GENOMIC_MATRIX,
					['isTransposed'], true) :
		fileInfo;
};

// Note that this will emit lines including the end-of-line markers,
// and will append empty lines to the end of the previous non-empty
// line, like 'foo\n\nbar\n' -> ['foo\n\n', 'bar\n']
//
// Have tried a dozen different ways of doing this with regexes, via split,
// match, and exec. split and match are hard to get right, without a lot of
// irrelevant complexity. exec works, but the chrome regex engine has wildly
// variable performance characteristics depending on the input.
export function getLines(chunk, lines = []) {
	var i = chunk.search(/[\n\r]/);
	if (i === -1) {
		return {lines, remainder: chunk};
	}

	do {
		i += 1;
	} while (i < chunk.length && (chunk[i] === '\n' || chunk[i] === '\r'));

	lines.push(chunk.slice(0, i));

	return getLines(chunk.slice(i), lines);
}

// Takes a stream of text buffers.
// Returns a stream of lines (synchronous).
var toLines = chunks => {
	var rem = '';
	return chunks.concatMap(chunk => {
			var {lines, remainder} = getLines(chunk),
				ret = [rem + lines[0], ...lines.slice(1)];
			rem = remainder;
			return from(ret);
		}).concat(defer(() => rem.length ? of(rem) : EMPTY));
};

//var chunkSize = 40 * 1000 * 1000;
var chunkSize = 10 * 1000 * 1000;

// Take a stream of strings, returning a stream of buffers
// of size chunkSize.
var fillBuffer = seq => {
	var buff = '';
	return seq.concatMap(s => {
		var over = buff.length + s.length - chunkSize,
		   ret = over > 0 ? of(buff + s.slice(0, -over)) : EMPTY;
		buff = over > 0 ? s.slice(-over) : buff + s;
		return ret;
	}).concat(defer(() => buff.length ? of(buff) : EMPTY));
};

var sendFn = fileName => buffer =>
	buffer.concatMap((blob, i) => {
			var formData = new FormData();

			formData.append("file", blob, fileName);
			if (i !== 0) {
				formData.append('append', 'true');
			}
			return postFile(formData);
		});

function bufferTransposedFile(fileName, file, dataType, result, metrics) {
	var count = Math.ceil(file.size / chunkSize),
		cols = metrics.header.length,
		rows = metrics.c0.length + 1,
		caat = Math.min(cols, ~~(chunkSize / (file.size / cols))), // # columns at a time, to read
		passes = Math.ceil(cols / caat),
		bufferFn = chunks => {
			var lines = chunks.windowCount(count)
				// should probably use a toLines method that cuts the newlines
				.concatMap(columnChunks => toLines(columnChunks))
				.map((l, i) => Let((pass = ~~(i / rows)) => l.split(/\t/).slice(pass * caat, (pass + 1) * caat)))
				.bufferCount(rows).concatMap(arr => arr[0].map((_, i) => arr.map(a => a[i].replace(/[\r\n]+$/, '')).join('\t') + '\n'));
			// kind of silly that we join the lines here, then split them in getErrors.
			// Need to ditch the running checks in getErrors. Or expand
			// them to something more useful.
			return fillBuffer(lines.do(getErrors(dataType, result)))
				.map(buff => new Blob([buff]));
		};
	return backPressure(
		iterable.repeat(passes, chunkReader(file, chunkSize)),
		2,
		readFileObs,
		bufferFn,
		sendFn(fileName));
}
// If sending to server is slower than reading from disk, it's better to
// compute the Blob in bufferFn (vs. sendFn), so it's immediately ready
// to send when the previous query completes.
function bufferFile(fileName, file, dataType, result) {
	var bufferFn = chunks => fillBuffer(toLines(chunks).do(getErrors(dataType, result)))
		.map(buff => new Blob([buff]));
	return backPressure(
		chunkReader(file, chunkSize), 2,
		readFileObs,
		bufferFn,
		sendFn(fileName));
}

function serverError(err) {
	console.log(err);
	return {
		serverError: err.message
	};
}

// XXX fix the inference/snippet on 1st read
const importFile = (fileInfo, ignoreWarnings, metrics) => {
	const {fileName, file, form, isTransposed} = matchFormat(fileInfo),
		{dataType} = form,
		result = {}, // error check result, which is updated imperatively.
		bufferFn = isTransposed ? bufferTransposedFile : bufferFile,
		upload = bufferFn(fileName, file, dataType, result, metrics)
			.last()
			.concatMap(() => {
				var formData = new FormData();
				formData.append("file", new Blob([createMetaDataFile(form)]), fileName + '.json');
				return postFile(formData);
			});

		return upload.concatMap(() =>
			Let(({errors, warnings, snippets} = result) =>
				errors.length ? of({errors, snippets}) :
				warnings.length && !ignoreWarnings ? of({warnings}) :
				updateFile(fileName)))
			.catch(error => Rx.Observable.of(serverError(error)));
};

const download = (host, name) => Rx.Observable.ajax({
    url: host + '/download/' + name,
    responseType: 'text',
    method: 'GET',
    crossDomain: true
});

const uploadProbemapFile = ({ name }) => {
    return Rx.Observable.zip(download(referenceHost, name), download(referenceHost, name + '.json'))
        .flatMap(([data, json]) => {
            const formData = new FormData();
            var fileName = getBaseProbemapName(name);

            formData.append("file", new Blob([data.response]), fileName);
            formData.append("file", new Blob([json.response]), fileName + '.json');

            return postFile(formData).switchMapTo(updateFile(fileName));
        }).catch(error => Rx.Observable.of({probemapError: error.message}));
};

// XXX This is a bit rough, just demonstrating how we might approach incorporating
// the inferences on data format. We may get a suggestion of where the samples are
// (1st column or 1st row), an ordered list of matching probemaps (or undefined),
// and an ordered list of matching cohorts (or undefined). We stash all of these,
// and pre-populate some form fields, like cohortRadio and fileFormat. We could
// pre-poulate probemap, but due to the current probemap wizard screen it would
// require first identifying the correct radio button (gene/transcript or probe).
// We probably want to refactor that screen, instead. There's more we might
// infer, e.g. if we match probemaps, it probably isn't a segmented or mutation
// dataset, and we could drop those from the data type selection.
const inferForm = (knownProbemaps, firstRow, firstColumn) => {
	var inference = infer(firstRow, firstColumn),
		{samples, probemaps, cohorts} = inference || {},
		inferredOrientation = samples === 'row' ?
				  GENOMIC_MATRIX : undefined,
		cohort = getIn(cohorts, [0, 'name']),
        probemap = getIn(probemaps, [0, 'name']);

    probemap = getIn(
            knownProbemaps.find(p => getIn(p, ['value', 'name']) === probemap),
            ['value']
        );

	return {
		recommended: inference,
		form: {
			fileFormat: inferredOrientation,
			cohortRadio: cohort ? 'publicCohort' : undefined,
			publicCohort: cohort,
			probemap
		}
	};
};

// XXX copied from staticCompoents. Put in a common place.
var maxColumns = 6,
	maxNumRows = 20;

var file; // XXX local state
var metrics;

var cmds = {
	loadFile: (probemaps, fileHandle) => {
		file = fileHandle;
		return toLines(from(chunkReader(fileHandle, chunkSize), asap).
				concatMap(readFileObs))
			.reduce((acc, line, i) => {
				if (i === 0) {
					acc.header = line.replace(/[\n\r]+$/, '').split(/\t/);
					acc.snippet = [acc.header.slice(0, maxColumns)];
					acc.c0 = [];
				} else if (i < maxNumRows) {
					let c = line.split(/\t/, maxColumns);
					acc.snippet.push(c);
					acc.c0.push(copyStr(c[0]));
				} else {
					let c = line.split(/\t/, 1);
					acc.c0.push(copyStr(c[0]));
				}
				return acc;
			}, {})
			.do(acc => metrics = acc) // XXX stash for later use when loading
			.map(({header, c0, snippet}) =>
					[inferForm(probemaps, header, c0), snippet]);
	},
	postFile: (fileName, form, localProbemaps, ignoreWarnings) => {
		const params = {form, fileName, file},
			doImport = importFile(params, ignoreWarnings, metrics);
		return form.probemap && !has(localProbemaps, form.probemap.hash) ?
			uploadProbemapFile(form.probemap).switchMap(
				pm => has(pm, 'probemapError') ? Rx.Observable.of(pm) :
					doImport) :
			doImport;
	}
};

// XXX catch errors here? would need to flag them somehow &
// extract enough info to be serializable.
// esp. need to handle file read errors.
const wrapSlotRequest = ({msg: [tag, ...args], id}) =>
	// using defer so we can capture any errors thrown in the handlers
	defer(() => cmds[tag](...args)).catch(err => of(serverError(err))).map(msg => ({msg, id}));

var recvQ = fromEvent(self, 'message').map(({data}) => data);

recvQ.groupBy(({msg: [tag]}) => tag)
	.map(g => g.switchMap(wrapSlotRequest)) // cancel old requests on same method
	.mergeAll()
	.subscribe(ev => postMessage(ev));
<commit_msg>bug fix: re-import probeMap when the localhost existing probemap has an error<commit_after>'use strict';

import 'babel-polyfill';
import {servers} from '../defaultServers';
import Rx from '../rx';
var {ajax, create, defer, empty, from, fromEvent, of} = Rx.Observable;
var EMPTY = empty();
var {asap} = Rx.Scheduler;
import getErrors from '../import/errorChecking';
import infer from '../import/infer.js';
import {dataSubType, FILE_FORMAT} from '../import/constants';
const {GENOMIC_MATRIX, CLINICAL_MATRIX} = FILE_FORMAT;
import {Let, assocIn, copyStr, getIn, has, iterable} from '../underscore_ext';
import backPressure from '../import/backPressure';
import chunkReader from '../import/chunkReader';
require('./html5-formdata-polyfill'); // for safari

const referenceHost = 'https://reference.xenahubs.net';

const getBaseProbemapName = name => name ? name.replace(/(.*[/])/, '') : null;

const createMetaDataFile = (state) => {
    const probemap = getBaseProbemapName(getIn(state, ['probemap', 'name']));
    return JSON.stringify({
        cohort: state[state.cohortRadio],
        dataSubType: dataSubType[state.dataType],
        type: state.fileFormat,
        assembly: state.assembly || void 0,
        probemap: probemap ? probemap : void 0
    }, null, 4);
};

const postFile = (file) => {
    const payload = {
        url: `${servers.localHub}/upload/`,
        body: file,
        responseType: 'text',
        method: 'POST',
        crossDomain: true
    };

    return ajax(payload).map(r => r.response);
};

const updateFile = (fileName) => {
    const payload = {
        url: `${servers.localHub}/update/`,
        body: {'file': fileName},
        responseType: 'text',
        method: 'POST',
        crossDomain: true
    };
    return ajax(payload).map(r => JSON.parse(r.response));
};

const readFileObs = fileHandle => {
    return create(obs => {
		var reading = true;
        const reader = new FileReader();
        reader.onload = (e) => {
			reading = false;
            obs.next(e.target.result);
            obs.complete();
        };
        reader.onerror = ev => obs.error(ev.target.error);
        reader.readAsBinaryString(fileHandle);
        return () => reading && reader.abort();
    });
};

const matchFormat = fileInfo => {
	const {form: {fileFormat, dataType}} = fileInfo;
	return fileFormat === GENOMIC_MATRIX && dataType === 'phenotype' ?
			assocIn(fileInfo,
					['form', 'fileFormat'], CLINICAL_MATRIX,
					['isTransposed'], true) :
		fileFormat === CLINICAL_MATRIX && dataType !== 'phenotype' ?
			assocIn(fileInfo,
					['form', 'fileFormat'], GENOMIC_MATRIX,
					['isTransposed'], true) :
		fileInfo;
};

// Note that this will emit lines including the end-of-line markers,
// and will append empty lines to the end of the previous non-empty
// line, like 'foo\n\nbar\n' -> ['foo\n\n', 'bar\n']
//
// Have tried a dozen different ways of doing this with regexes, via split,
// match, and exec. split and match are hard to get right, without a lot of
// irrelevant complexity. exec works, but the chrome regex engine has wildly
// variable performance characteristics depending on the input.
export function getLines(chunk, lines = []) {
	var i = chunk.search(/[\n\r]/);
	if (i === -1) {
		return {lines, remainder: chunk};
	}

	do {
		i += 1;
	} while (i < chunk.length && (chunk[i] === '\n' || chunk[i] === '\r'));

	lines.push(chunk.slice(0, i));

	return getLines(chunk.slice(i), lines);
}

// Takes a stream of text buffers.
// Returns a stream of lines (synchronous).
var toLines = chunks => {
	var rem = '';
	return chunks.concatMap(chunk => {
			var {lines, remainder} = getLines(chunk),
				ret = [rem + lines[0], ...lines.slice(1)];
			rem = remainder;
			return from(ret);
		}).concat(defer(() => rem.length ? of(rem) : EMPTY));
};

//var chunkSize = 40 * 1000 * 1000;
var chunkSize = 10 * 1000 * 1000;

// Take a stream of strings, returning a stream of buffers
// of size chunkSize.
var fillBuffer = seq => {
	var buff = '';
	return seq.concatMap(s => {
		var over = buff.length + s.length - chunkSize,
		   ret = over > 0 ? of(buff + s.slice(0, -over)) : EMPTY;
		buff = over > 0 ? s.slice(-over) : buff + s;
		return ret;
	}).concat(defer(() => buff.length ? of(buff) : EMPTY));
};

var sendFn = fileName => buffer =>
	buffer.concatMap((blob, i) => {
			var formData = new FormData();

			formData.append("file", blob, fileName);
			if (i !== 0) {
				formData.append('append', 'true');
			}
			return postFile(formData);
		});

function bufferTransposedFile(fileName, file, dataType, result, metrics) {
	var count = Math.ceil(file.size / chunkSize),
		cols = metrics.header.length,
		rows = metrics.c0.length + 1,
		caat = Math.min(cols, ~~(chunkSize / (file.size / cols))), // # columns at a time, to read
		passes = Math.ceil(cols / caat),
		bufferFn = chunks => {
			var lines = chunks.windowCount(count)
				// should probably use a toLines method that cuts the newlines
				.concatMap(columnChunks => toLines(columnChunks))
				.map((l, i) => Let((pass = ~~(i / rows)) => l.split(/\t/).slice(pass * caat, (pass + 1) * caat)))
				.bufferCount(rows).concatMap(arr => arr[0].map((_, i) => arr.map(a => a[i].replace(/[\r\n]+$/, '')).join('\t') + '\n'));
			// kind of silly that we join the lines here, then split them in getErrors.
			// Need to ditch the running checks in getErrors. Or expand
			// them to something more useful.
			return fillBuffer(lines.do(getErrors(dataType, result)))
				.map(buff => new Blob([buff]));
		};
	return backPressure(
		iterable.repeat(passes, chunkReader(file, chunkSize)),
		2,
		readFileObs,
		bufferFn,
		sendFn(fileName));
}
// If sending to server is slower than reading from disk, it's better to
// compute the Blob in bufferFn (vs. sendFn), so it's immediately ready
// to send when the previous query completes.
function bufferFile(fileName, file, dataType, result) {
	var bufferFn = chunks => fillBuffer(toLines(chunks).do(getErrors(dataType, result)))
		.map(buff => new Blob([buff]));
	return backPressure(
		chunkReader(file, chunkSize), 2,
		readFileObs,
		bufferFn,
		sendFn(fileName));
}

function serverError(err) {
	console.log(err);
	return {
		serverError: err.message
	};
}

// XXX fix the inference/snippet on 1st read
const importFile = (fileInfo, ignoreWarnings, metrics) => {
	const {fileName, file, form, isTransposed} = matchFormat(fileInfo),
		{dataType} = form,
		result = {}, // error check result, which is updated imperatively.
		bufferFn = isTransposed ? bufferTransposedFile : bufferFile,
		upload = bufferFn(fileName, file, dataType, result, metrics)
			.last()
			.concatMap(() => {
				var formData = new FormData();
				formData.append("file", new Blob([createMetaDataFile(form)]), fileName + '.json');
				return postFile(formData);
			});

		return upload.concatMap(() =>
			Let(({errors, warnings, snippets} = result) =>
				errors.length ? of({errors, snippets}) :
				warnings.length && !ignoreWarnings ? of({warnings}) :
				updateFile(fileName)))
			.catch(error => Rx.Observable.of(serverError(error)));
};

const download = (host, name) => Rx.Observable.ajax({
    url: host + '/download/' + name,
    responseType: 'text',
    method: 'GET',
    crossDomain: true
});

const uploadProbemapFile = ({ name }) => {
    return Rx.Observable.zip(download(referenceHost, name), download(referenceHost, name + '.json'))
        .flatMap(([data, json]) => {
            const formData = new FormData();
            var fileName = getBaseProbemapName(name);

            formData.append("file", new Blob([data.response]), fileName);
            formData.append("file", new Blob([json.response]), fileName + '.json');

            return postFile(formData).switchMapTo(updateFile(fileName));
        }).catch(error => Rx.Observable.of({probemapError: error.message}));
};

// XXX This is a bit rough, just demonstrating how we might approach incorporating
// the inferences on data format. We may get a suggestion of where the samples are
// (1st column or 1st row), an ordered list of matching probemaps (or undefined),
// and an ordered list of matching cohorts (or undefined). We stash all of these,
// and pre-populate some form fields, like cohortRadio and fileFormat. We could
// pre-poulate probemap, but due to the current probemap wizard screen it would
// require first identifying the correct radio button (gene/transcript or probe).
// We probably want to refactor that screen, instead. There's more we might
// infer, e.g. if we match probemaps, it probably isn't a segmented or mutation
// dataset, and we could drop those from the data type selection.
const inferForm = (knownProbemaps, firstRow, firstColumn) => {
	var inference = infer(firstRow, firstColumn),
		{samples, probemaps, cohorts} = inference || {},
		inferredOrientation = samples === 'row' ?
				  GENOMIC_MATRIX : undefined,
		cohort = getIn(cohorts, [0, 'name']),
        probemap = getIn(probemaps, [0, 'name']);

    probemap = getIn(
            knownProbemaps.find(p => getIn(p, ['value', 'name']) === probemap),
            ['value']
        );

	return {
		recommended: inference,
		form: {
			fileFormat: inferredOrientation,
			cohortRadio: cohort ? 'publicCohort' : undefined,
			publicCohort: cohort,
			probemap
		}
	};
};

// XXX copied from staticCompoents. Put in a common place.
var maxColumns = 6,
	maxNumRows = 20;

var file; // XXX local state
var metrics;

var cmds = {
	loadFile: (probemaps, fileHandle) => {
		file = fileHandle;
		return toLines(from(chunkReader(fileHandle, chunkSize), asap).
				concatMap(readFileObs))
			.reduce((acc, line, i) => {
				if (i === 0) {
					acc.header = line.replace(/[\n\r]+$/, '').split(/\t/);
					acc.snippet = [acc.header.slice(0, maxColumns)];
					acc.c0 = [];
				} else if (i < maxNumRows) {
					let c = line.split(/\t/, maxColumns);
					acc.snippet.push(c);
					acc.c0.push(copyStr(c[0]));
				} else {
					let c = line.split(/\t/, 1);
					acc.c0.push(copyStr(c[0]));
				}
				return acc;
			}, {})
			.do(acc => metrics = acc) // XXX stash for later use when loading
			.map(({header, c0, snippet}) =>
					[inferForm(probemaps, header, c0), snippet]);
	},
	postFile: (fileName, form, localProbemaps, ignoreWarnings) => {
		const params = {form, fileName, file},
			doImport = importFile(params, ignoreWarnings, metrics);
		return form.probemap && (!has(localProbemaps, form.probemap.hash) || has(localProbemaps[form.probemap.hash], 'error')) ?
			uploadProbemapFile(form.probemap).switchMap(
				pm => has(pm, 'probemapError') ? Rx.Observable.of(pm) :
					doImport) :
			doImport;
	}
};

// XXX catch errors here? would need to flag them somehow &
// extract enough info to be serializable.
// esp. need to handle file read errors.
const wrapSlotRequest = ({msg: [tag, ...args], id}) =>
	// using defer so we can capture any errors thrown in the handlers
	defer(() => cmds[tag](...args)).catch(err => of(serverError(err))).map(msg => ({msg, id}));

var recvQ = fromEvent(self, 'message').map(({data}) => data);

recvQ.groupBy(({msg: [tag]}) => tag)
	.map(g => g.switchMap(wrapSlotRequest)) // cancel old requests on same method
	.mergeAll()
	.subscribe(ev => postMessage(ev));
<|endoftext|>


<commit_before>MyApp.define('Greetings.Printer.DOM', function (require) {
    var $ = require('jQuery');

    this.exports = function (text, color) {
        $(document).ready(function () {
            $('<div>')
                .text(text)
                .css({color: 'red'})
                .appendTo('body');
        });
    };
});<commit_msg>Update dom_printer.js<commit_after>MyApp.define('Greetings.Printer.DOM', function (require) {
    var $ = require('jQuery');

    this.exports = function (text, color) {
        $(document).ready(function () {
            $('<div>')
                .text(text)
                .css({color: color})
                .appendTo('body');
        });
    };
});
<|endoftext|>


<commit_before>import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core'
import Grid from '@material-ui/core/Grid'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'

import { Label2 } from 'src/components/typography'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
import { fromNamespace } from 'src/utils/config'

import styles from './Footer.styles'
const GET_DATA = gql`
  query getData {
    cryptoRates
    cryptoCurrencies {
      code
      display
    }
    config
    accountsConfig {
      code
      display
    }
  }
`
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })

const useStyles = makeStyles(styles)
const Footer = () => {
  const { data, loading } = useQuery(GET_DATA)
  const [expanded, setExpanded] = useState(false)
  const [delayedExpand, setDelayedExpand] = useState(null)

  const classes = useStyles({
    bigFooter: R.keys(data?.cryptoRates?.withCommissions).length > 8,
    expanded
  })

  const canExpand = R.keys(data?.cryptoRates.withCommissions ?? []).length > 4

  const wallets = fromNamespace('wallets')(data?.config)

  const renderFooterItem = key => {
    const idx = R.findIndex(R.propEq('code', key))(data.cryptoCurrencies)
    const tickerCode = wallets[`${key}_ticker`]
    const tickerIdx = R.findIndex(R.propEq('code', tickerCode))(
      data.accountsConfig
    )

    const tickerName = data.accountsConfig[tickerIdx].display

    const cashInNoCommission = parseFloat(
      R.path(['cryptoRates', 'withoutCommissions', key, 'cashIn'])(data)
    )
    const cashOutNoCommission = parseFloat(
      R.path(['cryptoRates', 'withoutCommissions', key, 'cashOut'])(data)
    )

    const avgOfAskBid = new BigNumber(
      (cashInNoCommission + cashOutNoCommission) / 2
    ).toFormat(2)
    const cashIn = new BigNumber(
      parseFloat(
        R.path(['cryptoRates', 'withCommissions', key, 'cashIn'])(data)
      )
    ).toFormat(2)
    const cashOut = new BigNumber(
      parseFloat(
        R.path(['cryptoRates', 'withCommissions', key, 'cashOut'])(data)
      )
    ).toFormat(2)

    const localeFiatCurrency = data.config.locale_fiatCurrency

    return (
      <Grid key={key} item xs={3}>
        <Label2 className={classes.label}>
          {data.cryptoCurrencies[idx].display}
        </Label2>

            <TxInIcon />
            <Label2>{` ${cashIn} ${localeFiatCurrency}`}</Label2>

            <TxOutIcon />
            <Label2>{` ${cashOut} ${localeFiatCurrency}`}</Label2>

        <Label2
          className={
            classes.tickerLabel
          }>{`${tickerName}: ${avgOfAskBid} ${localeFiatCurrency}`}</Label2>
      </Grid>
    )
  }

  const handleMouseEnter = () => {
    setDelayedExpand(setTimeout(() => canExpand && setExpanded(true), 300))
  }

  const handleMouseLeave = () => {
    clearTimeout(delayedExpand)
    setExpanded(false)
  }

  return (
    <>

        {!loading && data && (
          <Grid container spacing={1}>
            <Grid container className={classes.footerContainer}>
              {R.keys(data.cryptoRates.withCommissions).map(key =>
                renderFooterItem(key)
              )}
            </Grid>
          </Grid>
        )}

    </>
  )
}

export default Footer
<commit_msg>Fix: rare footer crash in dashboard page<commit_after>import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core'
import Grid from '@material-ui/core/Grid'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'

import { Label2 } from 'src/components/typography'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
import { fromNamespace } from 'src/utils/config'

import styles from './Footer.styles'
const GET_DATA = gql`
  query getData {
    cryptoRates
    cryptoCurrencies {
      code
      display
    }
    config
    accountsConfig {
      code
      display
    }
  }
`
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })

const useStyles = makeStyles(styles)
const Footer = () => {
  const { data } = useQuery(GET_DATA)
  const [expanded, setExpanded] = useState(false)
  const [delayedExpand, setDelayedExpand] = useState(null)

  const withCommissions = R.path(['cryptoRates', 'withCommissions'])(data) ?? {}
  const classes = useStyles({
    bigFooter: R.keys(withCommissions).length > 8,
    expanded
  })
  const config = R.path(['config'])(data) ?? {}
  const canExpand = R.keys(withCommissions).length > 4

  const wallets = fromNamespace('wallets')(config)
  const cryptoCurrencies = R.path(['cryptoCurrencies'])(data) ?? []
  const accountsConfig = R.path(['accountsConfig'])(data) ?? []
  const localeFiatCurrency = R.path(['locale_fiatCurrency'])(config) ?? ''

  const renderFooterItem = key => {
    const idx = R.findIndex(R.propEq('code', key))(cryptoCurrencies)
    const tickerCode = wallets[`${key}_ticker`]
    const tickerIdx = R.findIndex(R.propEq('code', tickerCode))(accountsConfig)

    const tickerName = tickerIdx > -1 ? accountsConfig[tickerIdx].display : ''

    const cashInNoCommission = parseFloat(
      R.path(['cryptoRates', 'withoutCommissions', key, 'cashIn'])(data)
    )
    const cashOutNoCommission = parseFloat(
      R.path(['cryptoRates', 'withoutCommissions', key, 'cashOut'])(data)
    )

    const avgOfAskBid = new BigNumber(
      (cashInNoCommission + cashOutNoCommission) / 2
    ).toFormat(2)
    const cashIn = new BigNumber(
      parseFloat(
        R.path(['cryptoRates', 'withCommissions', key, 'cashIn'])(data)
      )
    ).toFormat(2)
    const cashOut = new BigNumber(
      parseFloat(
        R.path(['cryptoRates', 'withCommissions', key, 'cashOut'])(data)
      )
    ).toFormat(2)

    return (
      <Grid key={key} item xs={3}>
        <Label2 className={classes.label}>
          {cryptoCurrencies[idx].display}
        </Label2>

            <TxInIcon />
            <Label2>{` ${cashIn} ${localeFiatCurrency}`}</Label2>

            <TxOutIcon />
            <Label2>{` ${cashOut} ${localeFiatCurrency}`}</Label2>

        <Label2
          className={
            classes.tickerLabel
          }>{`${tickerName}: ${avgOfAskBid} ${localeFiatCurrency}`}</Label2>
      </Grid>
    )
  }

  const handleMouseEnter = () => {
    setDelayedExpand(setTimeout(() => canExpand && setExpanded(true), 300))
  }

  const handleMouseLeave = () => {
    clearTimeout(delayedExpand)
    setExpanded(false)
  }

  return (
    <>

        <Grid container spacing={1}>
          <Grid container className={classes.footerContainer}>
            {R.keys(withCommissions).map(key => renderFooterItem(key))}
          </Grid>
        </Grid>

    </>
  )
}

export default Footer
<|endoftext|>


<commit_before>var map;

$(document).ready(function() {

  $('.md-modal').addClass('md-show');
  $('.sbg-help').on('click', function(e) {
    $('.md-modal').addClass('md-show');
  });
  $('.md-close').on('click', function(e) {
    $('.md-modal').removeClass('md-show');
  });

  var mapOptions;
  mapOptions = void 0;
  mapOptions = {
    zoom: 2,
    center: new google.maps.LatLng(5, -30),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    zoomControl: true,
    disableDefaultUI: true
  };

  map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

  // var host = location.origin.replace(/^http/, 'ws');
  var socket = io.connect('http://singular.goel.im/');
  socket.on('newTweet', function (data) {
    console.log(data);
    var marker = addToMap(data);
    addToSidebar(data, marker);
  });

});

function addToMap(data) {
  var coords = data.coordinates;  // lng, lat
  var lat = coords[1];
  var lng = coords[0];

  var zoom = map.getZoom();
  var marker = new google.maps.Marker({
    position: new google.maps.LatLng(lat, lng),
    title: data.song_title,
    raiseOnDrag: false,
    draggable: false,
    animation: google.maps.Animation.DROP,
    map: map
  });

  marker.setIcon(new google.maps.MarkerImage(
    "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|FF0000",
    null, null, null, new google.maps.Size(15, 24)
  ));

  // create the tooltip
  createInfoWindow(marker, data);
  return marker;
}

function addToSidebar(data, marker) {
  console.log("adding to sidebar");
  var userImg = '';
  var username = '' + data.tweeted_by + '';
  var playerCode;
  if (data.song_source === 'sc') {
    playerCode = '';
  } else {
    playerCode = '';
  }
  var html = '

' + userImg + username + '
' + playerCode + '

';
  $('#allsongs').prepend(html);

  $('#' + data.tweet_id).bind("mouseenter", function (event) {
    google.maps.event.trigger(marker, 'click');
  });
}

var lastOpenInfoWin = null;

function createInfoWindow(marker, data) {
  //create an infowindow for this marker
  var content = '
@' + data.tweeted_by + '
' + data.song_title + '
';
  var infowindow = new google.maps.InfoWindow({
    content: content,
    maxWidth: 150
  });
  //open infowindo on click event on marker.
  google.maps.event.addListener(marker, 'click', function() {
    if(lastOpenInfoWin) {
      lastOpenInfoWin.close();
    }
    lastOpenInfoWin = infowindow;
    return infowindow.open(marker.get('map'), marker);
  });
}
<commit_msg>add new debug print<commit_after>var map;

$(document).ready(function() {

  $('.md-modal').addClass('md-show');
  $('.sbg-help').on('click', function(e) {
    $('.md-modal').addClass('md-show');
  });
  $('.md-close').on('click', function(e) {
    $('.md-modal').removeClass('md-show');
  });

  var mapOptions;
  mapOptions = void 0;
  mapOptions = {
    zoom: 2,
    center: new google.maps.LatLng(5, -30),
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    zoomControl: true,
    disableDefaultUI: true
  };

  map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

  // var host = location.origin.replace(/^http/, 'ws');
  var socket = io.connect('http://singular.goel.im/');
  console.log(server);

  socket.on('newTweet', function (data) {
    console.log(data);
    var marker = addToMap(data);
    addToSidebar(data, marker);
  });

});

function addToMap(data) {
  var coords = data.coordinates;  // lng, lat
  var lat = coords[1];
  var lng = coords[0];

  var zoom = map.getZoom();
  var marker = new google.maps.Marker({
    position: new google.maps.LatLng(lat, lng),
    title: data.song_title,
    raiseOnDrag: false,
    draggable: false,
    animation: google.maps.Animation.DROP,
    map: map
  });

  marker.setIcon(new google.maps.MarkerImage(
    "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|FF0000",
    null, null, null, new google.maps.Size(15, 24)
  ));

  // create the tooltip
  createInfoWindow(marker, data);
  return marker;
}

function addToSidebar(data, marker) {
  console.log("adding to sidebar");
  var userImg = '';
  var username = '' + data.tweeted_by + '';
  var playerCode;
  if (data.song_source === 'sc') {
    playerCode = '';
  } else {
    playerCode = '';
  }
  var html = '

' + userImg + username + '
' + playerCode + '

';
  $('#allsongs').prepend(html);

  $('#' + data.tweet_id).bind("mouseenter", function (event) {
    google.maps.event.trigger(marker, 'click');
  });
}

var lastOpenInfoWin = null;

function createInfoWindow(marker, data) {
  //create an infowindow for this marker
  var content = '
@' + data.tweeted_by + '
' + data.song_title + '
';
  var infowindow = new google.maps.InfoWindow({
    content: content,
    maxWidth: 150
  });
  //open infowindo on click event on marker.
  google.maps.event.addListener(marker, 'click', function() {
    if(lastOpenInfoWin) {
      lastOpenInfoWin.close();
    }
    lastOpenInfoWin = infowindow;
    return infowindow.open(marker.get('map'), marker);
  });
}
<|endoftext|>


<commit_before>var _ = require("./utils"),
    $Element = require("./element"),
    features = require("./features"),
    importStyles = require("./dom.importstyles"),
    et = features.WEBKIT_PREFIX ? ["webkitAnimationEnd", "webkitTransitionEnd"] : ["animationend", "transitionend"],
    animatioProps = ["transition-duration", "animation-duration", "animation-iteration-count"],
    prevDisplay = "_" + Date.now(),
    createCallback = function(el, callback, fn) {
        return function() {
            el.legacy(function(node, el, index, ref) {
                var hidden = typeof fn === "function" ? fn(el) : fn,
                    styles = el.style(animatioProps),
                    transitionDelay = parseFloat(styles[animatioProps[0]]),
                    animationDelay = parseFloat(styles[animatioProps[1]]),
                    iterationCount = parseFloat(styles[animatioProps[2]]),
                    completeCallback = function() {
                        if (hidden) {
                            // store current display value in private property
                            el[prevDisplay] = node.style.display;
                            // hide element and remove it from flow
                            node.style.display = "none";
                            node.style.visibility = "";
                        }

                        node.style.pointerEvents = "";

                        if (callback) callback(el, index, ref);
                    };

                if (features.CSS3_ANIMATIONS) {
                    // choose max delay to determine appropriate event type
                    el.once(et[iterationCount >= 1 && animationDelay > transitionDelay ? 0 : 1], completeCallback);
                } else {
                    // use setTimeout to make a safe call
                    setTimeout(completeCallback, 0);
                }

                if (hidden) {
                    // set visibility inline to override inherited from [aria-hidden=true]
                    node.style.visibility = "visible";
                } else {
                    if (!el[prevDisplay] || el[prevDisplay] === "none") el[prevDisplay] = "";

                    node.style.display = el[prevDisplay];

                    delete el[prevDisplay];
                }

                // set pointer-events:none during animation to prevent unexpected actions
                node.style.pointerEvents = "none";

                // trigger native CSS animation
                if (hidden) {
                    node.setAttribute("aria-hidden", hidden);
                } else {
                    // toggle aria-hidden async to apply inline styles above
                    // before starting an animation
                    setTimeout(function() { node.setAttribute("aria-hidden", hidden) }, 0);
                }
            });
        };
    },
    makeVisibilityMethod = function(name, fn) {
        return function(delay, callback) {
            var delayType = typeof delay;

            if (arguments.length === 1 && delayType === "function") {
                callback = delay;
                delay = 0;
            }

            if (delay && (delayType !== "number" || delay < 0) ||
                callback && typeof callback !== "function") {
                throw _.makeError(name);
            }

            callback = createCallback(this, callback, fn);

            if (delay) {
                setTimeout(callback, delay);
            } else {
                callback();
            }

            return this;
        };
    };

/**
 * Show element with optional callback and delay
 * @param {Number}   [delay=0]  time in miliseconds to wait
 * @param {Function} [callback] function that executes when animation is done
 * @return {$Element}
 * @function
 */
$Element.prototype.show = makeVisibilityMethod("show", false);

/**
 * Hide element with optional callback and delay
 * @param {Number}   [delay=0]  time in miliseconds to wait
 * @param {Function} [callback] function that executes when animation is done
 * @return {$Element}
 * @function
 */
$Element.prototype.hide = makeVisibilityMethod("hide", true);

/**
 * Toggle element visibility with optional callback and delay
 * @param {Number}   [delay=0]  time in miliseconds to wait
 * @param {Function} [callback] function that executes when animation is done
 * @return {$Element}
 * @function
 */
$Element.prototype.toggle = makeVisibilityMethod("toggle", function(el) {
    return el.get("aria-hidden") !== "true";
});
<commit_msg>various improvements in visibility methods, pass tests<commit_after>var _ = require("./utils"),
    $Element = require("./element"),
    features = require("./features"),
    et = features.WEBKIT_PREFIX ? ["webkitAnimationEnd", "webkitTransitionEnd"] : ["animationend", "transitionend"],
    animatioProps = ["transition-duration", "animation-duration", "animation-iteration-count"],
    prevDisplayValue = "_" + Date.now(),
    makeVisibilityMethod = function(name, fn) {
        return function(delay, callback) {
            var delayType = typeof delay;

            if (arguments.length === 1 && delayType === "function") {
                callback = delay;
                delay = 0;
            }

            if (delay && (delayType !== "number" || delay < 0) ||
                callback && typeof callback !== "function") {
                throw _.makeError(name);
            }

            return this.legacy(function(node, el, index, ref) {
                var hidden = typeof fn === "function" ? fn(node) : fn,
                    styles = el.style(animatioProps),
                    transitionDelay = parseFloat(styles[animatioProps[0]]),
                    animationDelay = parseFloat(styles[animatioProps[1]]),
                    iterationCount = parseFloat(styles[animatioProps[2]]),
                    hasAnimation = transitionDelay || iterationCount >= 1 && animationDelay,
                    completeCallback = function() {
                        if (hidden) {
                            // store current display value in private property
                            el[prevDisplayValue] = node.style.display;
                            // hide element and remove it from flow
                            node.style.display = "none";
                            node.style.visibility = "";
                        }

                        node.style.pointerEvents = "";

                        if (callback) callback(el, index, ref);
                    };

                if (features.CSS3_ANIMATIONS && hasAnimation) {
                    // choose max delay to determine appropriate event type
                    el.once(et[iterationCount >= 1 && animationDelay > transitionDelay ? 0 : 1], completeCallback);
                } else {
                    // use setTimeout to make a safe call when there is no animation
                    setTimeout(completeCallback, 0);
                }

                if (hidden) {
                    // set visibility inline to override inherited from [aria-hidden=true]
                    node.style.visibility = "visible";
                } else {
                    if (!el[prevDisplayValue] || el[prevDisplayValue] === "none") el[prevDisplayValue] = "";

                    node.style.display = el[prevDisplayValue];

                    delete el[prevDisplayValue];
                }

                // set pointer-events:none during animation to prevent unexpected actions
                node.style.pointerEvents = "none";

                // trigger native CSS animation
                if (hasAnimation || delay) {
                    // toggle aria-hidden async to apply inline styles before the animation start
                    setTimeout(function() { node.setAttribute("aria-hidden", hidden) }, delay);
                } else {
                    node.setAttribute("aria-hidden", hidden);
                }
            });
        };
    };

/**
 * Show element with optional callback and delay
 * @param {Number}   [delay=0]  time in miliseconds to wait
 * @param {Function} [callback] function that executes when animation is done
 * @return {$Element}
 * @function
 */
$Element.prototype.show = makeVisibilityMethod("show", false);

/**
 * Hide element with optional callback and delay
 * @param {Number}   [delay=0]  time in miliseconds to wait
 * @param {Function} [callback] function that executes when animation is done
 * @return {$Element}
 * @function
 */
$Element.prototype.hide = makeVisibilityMethod("hide", true);

/**
 * Toggle element visibility with optional callback and delay
 * @param {Number}   [delay=0]  time in miliseconds to wait
 * @param {Function} [callback] function that executes when animation is done
 * @return {$Element}
 * @function
 */
$Element.prototype.toggle = makeVisibilityMethod("toggle", function(node) {
    return node.getAttribute("aria-hidden") !== "true";
});
<|endoftext|>


<commit_before>'use strict';

define(['./module', 'darkwallet'], function (providers, DarkWallet) {

  function WalletProvider($scope) {
      this.addresses = {};
      this.allAddresses = [];
  }

  WalletProvider.prototype.onIdentityLoaded = function(identity) {
      this.addresses = {};
      this.allAddresses.splice(0, this.allAddresses.length);

      // load addresses for this identity
      this.loadAddresses(identity);
  }

  WalletProvider.prototype.loadAddresses = function(identity) {
      var self = this;
      // Init pockets
      for(var idx=0; idx<identity.wallet.pockets.hdPockets.length; idx++) {
          self.initPocket(idx);
      };
      /* Load addresses into angular */
      Object.keys(identity.wallet.pubKeys).forEach(function(pubKeyIndex) {
          var walletAddress = identity.wallet.getAddress(pubKeyIndex);
          // Regular addresses
          if (walletAddress.index.length > 1) {
              // add to scope
              var branchId = walletAddress.index[0];
              if (!self.addresses[branchId]) {
                  self.addresses[branchId] = [];
              }
              var addressArray = self.addresses[branchId];
              if (self.allAddresses.indexOf(walletAddress) == -1) {
                  addressArray.push(walletAddress);
                  self.allAddresses.push(walletAddress);
              }
          }
      });
  }

  /**
   * Instruct the DarkWallet to load an identity
   */
  WalletProvider.prototype.loadIdentity = function(callback) {
      // Load identity
      var identity = DarkWallet.getIdentity();
      if (identity) {
          callback(identity);
      } else {
          if (DarkWallet.getKeyRing().availableIdentities.length) {
              DarkWallet.core.loadIdentity(0);
          }
      }
  }

  /**
   * Generates (or load from cache) a new address
   * @param {Integer} branchId
   * @param {Integer} n
   * @returns {Object} Wallet address struct
   */
  WalletProvider.prototype.generateAddress = function(branchId, n) {
      if (!branchId) {
          branchId = 0;
      }
      if (!this.addresses[branchId]) {
          this.addresses[branchId] = [];
      }
      var addressArray = this.addresses[branchId];
      if (n === undefined || n === null) {
          n = addressArray.length;
      }
      var walletAddress = DarkWallet.getIdentity().wallet.getAddress([branchId, n]);

      // Init this address
      this.initAddress(walletAddress);
  };

  // initialize an address on the scope and the backend
  WalletProvider.prototype.removeAddress = function(walletAddress) {
      // remove from scope
      var thisCache = this.addresses[walletAddress.index[0]];
      var allCache = this.allAddresses;
      [thisCache, allCache].forEach(function(cache) {
          if (cache) {
              var idx = cache.indexOf(walletAddress);
              if (idx > 0) {
                  cache.splice(idx, 1);
              }
          }
      });

      // remove from backend
      DarkWallet.service.wallet.removeAddress(walletAddress);
      return walletAddress;
  };

  // remove an address from the scope and wallet
  WalletProvider.prototype.initAddress = function(walletAddress) {
       // add to scope
      this.addToScope(walletAddress);

      // get history for the new address
      DarkWallet.core.initAddress(walletAddress);
      return walletAddress;
  };

  // get a free change address or a new one
  WalletProvider.prototype.getChangeAddress = function(pocketId) {
      var identity = DarkWallet.getIdentity();
      if (!pocketId) pocketId = 0;
      var changeAddress = identity.wallet.getChangeAddress(pocketId);
      this.addToScope(changeAddress);
      return changeAddress;
  };

  // Initialize pocket structures.
  WalletProvider.prototype.initPocket = function(pocketId) {
      var branchId = pocketId*2;
      if (!this.addresses[branchId]) {
          this.addresses[branchId] = [];
      }
      if (!this.addresses[branchId+1]) {
          this.addresses[branchId+1] = [];
      }
  };

  // Add a wallet address to scope
  WalletProvider.prototype.addToScope = function(walletAddress) {
    var identity = DarkWallet.getIdentity();
    var pocketId = identity.wallet.pockets.getAddressPocketId(walletAddress);
    if (!walletAddress.type) {
        this.initPocket(pocketId);
    } else {
        if (!this.addresses[walletAddress.index[0]]) {
            this.addresses[walletAddress.index[0]] = [];
        }
    }
    var addressArray = this.addresses[walletAddress.index[0]];
    if (this.allAddresses.indexOf(walletAddress) == -1) {
        addressArray.push(walletAddress);
        this.allAddresses.push(walletAddress);
    }
  };

  providers.factory('$wallet', ['$rootScope', function($rootScope) {
    console.log("[WalletProvider] Initialize");
    return new WalletProvider($rootScope.$new());
  }]);
});
<commit_msg>return the newly created address.<commit_after>'use strict';

define(['./module', 'darkwallet'], function (providers, DarkWallet) {

  function WalletProvider($scope) {
      this.addresses = {};
      this.allAddresses = [];
  }

  WalletProvider.prototype.onIdentityLoaded = function(identity) {
      this.addresses = {};
      this.allAddresses.splice(0, this.allAddresses.length);

      // load addresses for this identity
      this.loadAddresses(identity);
  }

  WalletProvider.prototype.loadAddresses = function(identity) {
      var self = this;
      // Init pockets
      for(var idx=0; idx<identity.wallet.pockets.hdPockets.length; idx++) {
          self.initPocket(idx);
      };
      /* Load addresses into angular */
      Object.keys(identity.wallet.pubKeys).forEach(function(pubKeyIndex) {
          var walletAddress = identity.wallet.getAddress(pubKeyIndex);
          // Regular addresses
          if (walletAddress.index.length > 1) {
              // add to scope
              var branchId = walletAddress.index[0];
              if (!self.addresses[branchId]) {
                  self.addresses[branchId] = [];
              }
              var addressArray = self.addresses[branchId];
              if (self.allAddresses.indexOf(walletAddress) == -1) {
                  addressArray.push(walletAddress);
                  self.allAddresses.push(walletAddress);
              }
          }
      });
  }

  /**
   * Instruct the DarkWallet to load an identity
   */
  WalletProvider.prototype.loadIdentity = function(callback) {
      // Load identity
      var identity = DarkWallet.getIdentity();
      if (identity) {
          callback(identity);
      } else {
          if (DarkWallet.getKeyRing().availableIdentities.length) {
              DarkWallet.core.loadIdentity(0);
          }
      }
  }

  /**
   * Generates (or load from cache) a new address
   * @param {Integer} branchId
   * @param {Integer} n
   * @returns {Object} Wallet address struct
   */
  WalletProvider.prototype.generateAddress = function(branchId, n) {
      if (!branchId) {
          branchId = 0;
      }
      if (!this.addresses[branchId]) {
          this.addresses[branchId] = [];
      }
      var addressArray = this.addresses[branchId];
      if (n === undefined || n === null) {
          n = addressArray.length;
      }
      var walletAddress = DarkWallet.getIdentity().wallet.getAddress([branchId, n]);

      // Init this address
      this.initAddress(walletAddress);
      return walletAddress;
  };

  // initialize an address on the scope and the backend
  WalletProvider.prototype.removeAddress = function(walletAddress) {
      // remove from scope
      var thisCache = this.addresses[walletAddress.index[0]];
      var allCache = this.allAddresses;
      [thisCache, allCache].forEach(function(cache) {
          if (cache) {
              var idx = cache.indexOf(walletAddress);
              if (idx > 0) {
                  cache.splice(idx, 1);
              }
          }
      });

      // remove from backend
      DarkWallet.service.wallet.removeAddress(walletAddress);
      return walletAddress;
  };

  // remove an address from the scope and wallet
  WalletProvider.prototype.initAddress = function(walletAddress) {
       // add to scope
      this.addToScope(walletAddress);

      // get history for the new address
      DarkWallet.core.initAddress(walletAddress);
      return walletAddress;
  };

  // get a free change address or a new one
  WalletProvider.prototype.getChangeAddress = function(pocketId) {
      var identity = DarkWallet.getIdentity();
      if (!pocketId) pocketId = 0;
      var changeAddress = identity.wallet.getChangeAddress(pocketId);
      this.addToScope(changeAddress);
      return changeAddress;
  };

  // Initialize pocket structures.
  WalletProvider.prototype.initPocket = function(pocketId) {
      var branchId = pocketId*2;
      if (!this.addresses[branchId]) {
          this.addresses[branchId] = [];
      }
      if (!this.addresses[branchId+1]) {
          this.addresses[branchId+1] = [];
      }
  };

  // Add a wallet address to scope
  WalletProvider.prototype.addToScope = function(walletAddress) {
    var identity = DarkWallet.getIdentity();
    var pocketId = identity.wallet.pockets.getAddressPocketId(walletAddress);
    if (!walletAddress.type) {
        this.initPocket(pocketId);
    } else {
        if (!this.addresses[walletAddress.index[0]]) {
            this.addresses[walletAddress.index[0]] = [];
        }
    }
    var addressArray = this.addresses[walletAddress.index[0]];
    if (this.allAddresses.indexOf(walletAddress) == -1) {
        addressArray.push(walletAddress);
        this.allAddresses.push(walletAddress);
    }
  };

  providers.factory('$wallet', ['$rootScope', function($rootScope) {
    console.log("[WalletProvider] Initialize");
    return new WalletProvider($rootScope.$new());
  }]);
});
<|endoftext|>


<commit_before>var annontations = {
  GSW: {
    text: "GSW has lost by more\n than 13 points twice",
    textAnchor: 'end',
    textPos: [165, 135],
    path: 'M 190,90 C 190,130 190,130 170,130'
  },
  SAS: {
    text: "Spurs have only trailed \n 3 times at the end of the 3rd",
    textAnchor: 'middle',
    textPos: [120, 136],
    path: 'M 139,96 C 139,118 139,118 139,118'
  },
}

color = d3.scale.threshold()
    .domain([-12, -6, 0, 1, 7, 13])
    // .range(["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"])
    .range(["#b35806","#f1a340","#fee0b6","rgba(255,255,255,1)","#d8daeb","#998ec3","#542788"].reverse())

d3.select('#graph').append('div').style('margin-bottom', '20px').dataAppend(d3.range(-18, 19), 'div.key')
    .text(function(d){ return d < 0 ? -d : d == 0 ? '0' : d })
    .style('background', color)
    .filter(function(d){ return !d }).style('color', 'black')

d3.json('games.json', function(res){
  //only 2015
  games = res.filter(function(d){ return d.date[3] == '5' })

  teamGames = []
  games.forEach(function(game){
    game.team = game.away
    game.oppTeam = game.home
    teamGames.push(game)
    game.date = game.date.split('T')[0].replace('2015-', '').replace('-', '/')

    var flippedGame = {team: game.home, oppTeam: game.away, isFlipped: true, date: game.date}
    flippedGame.minutes = game.minutes.map(function(d){
        d.dif = d.h - d.v
        d.game = game
        return {min: d.min, dif: -d.dif, game: flippedGame, h: d.v, v: d.h}
      })
    teamGames.push(flippedGame)
  })

  byTeam = d3.nest().key(ƒ('team')).entries(teamGames)

  byTeam.forEach(function(team){
    team.byMinute = d3.nest().key(ƒ('min')).entries(_.flatten(team.values.map(ƒ('minutes'))))
    team.byMinute.forEach(function(minute){
      minute.values = _.sortBy(minute.values, 'dif')//.reverse()
      minute.numNeg = minute.values.filter(function(d){ return d.dif <  0 }).length
      minute.numTie = minute.values.filter(function(d){ return d.dif == 0 }).length
      minute.values.forEach(function(d, i){
        d.y = (i - Math.floor(minute.numTie/2) - minute.numNeg)
        d.i = i
      })
    })
    team.wins = team.values.filter(function(d){ return _.last(d.minutes).dif > 0 }).length
  })

  teamSel = d3.select('#graph').dataAppend(_.sortBy(byTeam, 'wins').reverse(), 'div.game')
  teamSel.append('div').text(ƒ('key'))//.style({'z-index': 3, position: 'relative'})

  teamSel.each(function(d, i){
    if (i > 4) return
    var c = d3.conventions({
      parentSel: d3.select(this),
      height: 150, width: 190,
      margin: {left: 10, top: 10, bottom: 10, right: 10}})

    c.x.domain([48, 0])
    c.y.domain([-20, 20])
    c.yAxis.tickValues([-15, 0, 15])
    c.yAxis.tickValues([])
    c.xAxis.tickValues([48, 36, 24, 12, 0])

    c.svg
      .on('mousemove', function(){
        console.log(d3.mouse(this))
      })
        .append('rect').attr({height: c.height, width: c.width, opacity: 0})

    c.drawAxis()

    var botLine = c.svg.append('path.hover-path')

    c.svg.selectAll('.x line').attr('y1', -c.height)
    // c.svg.append('path.zero').attr('d', ['M', [0, c.y(0)], 'h', c.width].join(''))

    var minuteSel = c.svg.dataAppend(d.byMinute, 'g.min')
        .translate(function(d){ return [c.x(d.key), c.y(-Math.floor(d.numTie/2) - d.numNeg) - c.y(0)] })
        // .style('opacity', function(d, i){ return d.key != 48 ? 1 : 0 })

    var line = d3.svg.line()
        .x(ƒ('min', c.x))
        .y(function(d){ return c.y(d.y) })
        .interpolate('step')

    var lineSel = c.svg.append('path.hover-path')
    var textG = c.svg.append('g.hover-text')
    var dateText  = textG.append('text').attr('y', '-1.1em')
    var oppText  = textG.append('text').attr('y', '-2.2em')
    var scoreText = textG.append('text')

    var botTextG = c.svg.append('g.hover-text')
    var botScore = botTextG.append('text').attr({'text-anchor': 'middle', 'y': '2.81em'})
    var botTime = botTextG.append('text').attr({'text-anchor': 'middle', 'y': '1.45em'})
    var circleSel = minuteSel.dataAppend(ƒ('values'), 'circle')
        .attr('cy', ƒ('i', c.y))
        .attr('r', 2)
        .attr('fill', ƒ('dif', color))
        .on('mouseover', function(d){
          // console.log(d)
          lineSel.attr('d', line(d.game.minutes))

          circleSel.classed('selected', function(e){ return e.game == d.game })

          var finalScore = _.last(d.game.minutes)
          textG.translate([c.width + 3, c.y(finalScore.y) + (d.game.minutes.length > 50 ? -10 : 0)])
          oppText.text((d.game.isFlipped ? ' ' : '@') + d.game.oppTeam)
          dateText.text(d.game.date)
          scoreText.text(finalScore.h + '-' + finalScore.v)

          botTextG.translate([c.x(d.min), c.height])
          botTime.text(d.min)
          botScore.text(d.h + '-' + d.v)
          botLine.attr('d', ['M', c.x(d.min), 0, 'v', c.height].join(' '))

        })
        // .call(d3.attachTooltip)

    var anno = annontations[d.key]
    if (!anno) return

    var annoG = c.svg.append('g.anno')
    annoG.append('text')
        .attr('text-anchor', anno.textAnchor)
        .translate(anno.textPos)
        .tspans(anno.text.split('\n'))

    annoG.append('path')
        .attr('d', anno.path)

  })

  d3.select(self.frameElement).style("height", 2000 + "px");
})

function clamp(a, b, c){ return Math.max(a, Math.min(b, c)) }
<commit_msg>adds min label<commit_after>var annontations = {
  GSW: {
    text: "GSW has lost by more\n than 13 points twice",
    textAnchor: 'end',
    textPos: [165, 135],
    path: 'M 190,90 C 190,130 190,130 170,130'
  },
  SAS: {
    text: "Spurs have only trailed \n 3 times at the end of the 3rd",
    textAnchor: 'middle',
    textPos: [120, 136],
    path: 'M 139,96 C 139,118 139,118 139,118'
  },
}

color = d3.scale.threshold()
    .domain([-12, -6, 0, 1, 7, 13])
    // .range(["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"])
    .range(["#b35806","#f1a340","#fee0b6","rgba(255,255,255,1)","#d8daeb","#998ec3","#542788"].reverse())

d3.select('#graph').append('div').style('margin-bottom', '20px').dataAppend(d3.range(-18, 19), 'div.key')
    .text(function(d){ return d < 0 ? -d : d == 0 ? '0' : d })
    .style('background', color)
    .filter(function(d){ return !d }).style('color', 'black')

d3.json('games.json', function(res){
  //only 2015
  games = res.filter(function(d){ return d.date[3] == '5' })

  teamGames = []
  games.forEach(function(game){
    game.team = game.away
    game.oppTeam = game.home
    teamGames.push(game)
    game.date = game.date.split('T')[0].replace('2015-', '').replace('-', '/')

    var flippedGame = {team: game.home, oppTeam: game.away, isFlipped: true, date: game.date}
    flippedGame.minutes = game.minutes.map(function(d){
        d.dif = d.h - d.v
        d.game = game
        return {min: d.min, dif: -d.dif, game: flippedGame, h: d.v, v: d.h}
      })
    teamGames.push(flippedGame)
  })

  byTeam = d3.nest().key(ƒ('team')).entries(teamGames)

  byTeam.forEach(function(team){
    team.byMinute = d3.nest().key(ƒ('min')).entries(_.flatten(team.values.map(ƒ('minutes'))))
    team.byMinute.forEach(function(minute){
      minute.values = _.sortBy(minute.values, 'dif')//.reverse()
      minute.numNeg = minute.values.filter(function(d){ return d.dif <  0 }).length
      minute.numTie = minute.values.filter(function(d){ return d.dif == 0 }).length
      minute.values.forEach(function(d, i){
        d.y = (i - Math.floor(minute.numTie/2) - minute.numNeg)
        d.i = i
      })
    })
    team.wins = team.values.filter(function(d){ return _.last(d.minutes).dif > 0 }).length
  })

  teamSel = d3.select('#graph').dataAppend(_.sortBy(byTeam, 'wins').reverse(), 'div.game')
  teamSel.append('div').text(ƒ('key'))//.style({'z-index': 3, position: 'relative'})

  teamSel.each(function(d, i){
    if (i > 4) return
    var c = d3.conventions({
      parentSel: d3.select(this),
      height: 150, width: 190,
      margin: {left: 10, top: 10, bottom: 10, right: 10}})

    c.x.domain([48, 0])
    c.y.domain([-20, 20])
    c.yAxis.tickValues([-15, 0, 15])
    c.yAxis.tickValues([])
    c.xAxis.tickValues([48, 36, 24, 12, 0])

    c.svg
      .on('mousemove', function(){
        console.log(d3.mouse(this))
      })
        .append('rect').attr({height: c.height, width: c.width, opacity: 0})

    c.drawAxis()

    var botLine = c.svg.append('path.hover-path')

    c.svg.selectAll('.x line').attr('y1', -c.height)
    c.svg.select('.x .tick').append('text').text('min').attr({y: 9, dy: '.71em', x: 10})
    // c.svg.append('path.zero').attr('d', ['M', [0, c.y(0)], 'h', c.width].join(''))

    var minuteSel = c.svg.dataAppend(d.byMinute, 'g.min')
        .translate(function(d){ return [c.x(d.key), c.y(-Math.floor(d.numTie/2) - d.numNeg) - c.y(0)] })
        // .style('opacity', function(d, i){ return d.key != 48 ? 1 : 0 })

    var line = d3.svg.line()
        .x(ƒ('min', c.x))
        .y(function(d){ return c.y(d.y) })
        .interpolate('step')

    var lineSel = c.svg.append('path.hover-path')
    var textG = c.svg.append('g.hover-text')
    var dateText  = textG.append('text').attr('y', '-1.1em')
    var oppText  = textG.append('text').attr('y', '-2.2em')
    var scoreText = textG.append('text')

    var botTextG = c.svg.append('g.hover-text')
    var botScore = botTextG.append('text').attr({'text-anchor': 'middle', 'y': '2.81em'})
    var botTime = botTextG.append('text').attr({'text-anchor': 'middle', 'y': '1.45em'})
    var circleSel = minuteSel.dataAppend(ƒ('values'), 'circle')
        .attr('cy', ƒ('i', c.y))
        .attr('r', 2)
        .attr('fill', ƒ('dif', color))
        .on('mouseover', function(d){
          // console.log(d)
          lineSel.attr('d', line(d.game.minutes))

          circleSel.classed('selected', function(e){ return e.game == d.game })

          var finalScore = _.last(d.game.minutes)
          textG.translate([c.width + 3, c.y(finalScore.y) + (d.game.minutes.length > 50 ? -10 : 0)])
          oppText.text((d.game.isFlipped ? ' ' : '@') + d.game.oppTeam)
          dateText.text(d.game.date)
          scoreText.text(finalScore.h + '-' + finalScore.v)

          botTextG.translate([c.x(d.min), c.height])
          botTime.text(d.min)
          botScore.text(d.h + '-' + d.v)
          botLine.attr('d', ['M', c.x(d.min), 0, 'v', c.height].join(' '))

        })
        // .call(d3.attachTooltip)

    var anno = annontations[d.key]
    if (!anno) return

    var annoG = c.svg.append('g.anno')
    annoG.append('text')
        .attr('text-anchor', anno.textAnchor)
        .translate(anno.textPos)
        .tspans(anno.text.split('\n'))

    annoG.append('path')
        .attr('d', anno.path)

  })

  d3.select(self.frameElement).style("height", 2000 + "px");
})

function clamp(a, b, c){ return Math.max(a, Math.min(b, c)) }
<|endoftext|>