Skip to content

Commit

Permalink
Fixed creating uptake reactions; improved and tested utils.clone
Browse files Browse the repository at this point in the history
Closes #101
  • Loading branch information
zakandrewking committed Feb 17, 2016
1 parent 5afc772 commit 3e2bd91
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 112 deletions.
137 changes: 65 additions & 72 deletions js/src/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -1353,89 +1353,96 @@ function delete_text_label_data(text_label_ids) {

// ---------------------------------------------------------------------
// Building
// ---------------------------------------------------------------------

/**
* Draw a reaction on a blank canvas.
* @param {String} starting_reaction - bigg_id for a reaction to draw.
* @param {Coords} coords - coordinates to start drawing
*/
function new_reaction_from_scratch(starting_reaction, coords, direction) {
/** Draw a reaction on a blank canvas.
starting_reaction: bigg_id for a reaction to draw.
coords: coordinates to start drawing
*/

// If there is no cobra model, error
if (!this.cobra_model) return console.error('No CobraModel. Cannot build new reaction');
if (!this.cobra_model) {
console.error('No CobraModel. Cannot build new reaction')
return
}

// set reaction coordinates and angle
// be sure to copy the reaction recursively
var cobra_reaction = utils.clone(this.cobra_model.reactions[starting_reaction]);
// Set reaction coordinates and angle. Be sure to clone the reaction.
var cobra_reaction = utils.clone(this.cobra_model.reactions[starting_reaction])

// check for empty reactions
if (_.size(cobra_reaction.metabolites) === 0)
throw Error('No metabolites in reaction ' + cobra_reaction.bigg_id)

// create the first node
for (var metabolite_id in cobra_reaction.metabolites) {
var coefficient = cobra_reaction.metabolites[metabolite_id],
metabolite = this.cobra_model.metabolites[metabolite_id];
if (coefficient < 0) {
var selected_node_id = String(++this.largest_ids.nodes),
label_d = { x: 30, y: 10 },
selected_node = { connected_segments: [],
x: coords.x,
y: coords.y,
node_is_primary: true,
label_x: coords.x + label_d.x,
label_y: coords.y + label_d.y,
name: metabolite.name,
bigg_id: metabolite_id,
node_type: 'metabolite' },
new_nodes = {};
new_nodes[selected_node_id] = selected_node;
break;
}
}
var reactant_ids = _.map(cobra_reaction.metabolites,
function (coeff, met_id) { return [ coeff, met_id ] })
.filter(function (x) { return x[0] < 0 }) // coeff < 0
.map(function(x) { return x[1] }) // metabolite id
// get the first reactant or else the first product
var metabolite_id = (reactant_ids.length > 0 ?
reactant_ids[0] :
Object.keys(cobra_reaction.metabolites)[0])
var metabolite = this.cobra_model.metabolites[metabolite_id]
var selected_node_id = String(++this.largest_ids.nodes)
var label_d = { x: 30, y: 10 }
var selected_node = { connected_segments: [],
x: coords.x,
y: coords.y,
node_is_primary: true,
label_x: coords.x + label_d.x,
label_y: coords.y + label_d.y,
name: metabolite.name,
bigg_id: metabolite_id,
node_type: 'metabolite' }
var new_nodes = {}
new_nodes[selected_node_id] = selected_node

// draw
extend_and_draw_metabolite.apply(this, [new_nodes, selected_node_id]);
extend_and_draw_metabolite.apply(this, [ new_nodes, selected_node_id ])

// clone the nodes and reactions, to redo this action later
var saved_nodes = utils.clone(new_nodes);
var saved_nodes = utils.clone(new_nodes)

// draw the reaction
var out = this.new_reaction_for_metabolite(starting_reaction,
selected_node_id,
direction, false),
reaction_redo = out.redo,
reaction_undo = out.undo;
reaction_undo = out.undo

// add to undo/redo stack
this.undo_stack.push(function() {
// undo
// first undo the reaction
reaction_undo();
reaction_undo()
// get the nodes to delete
this.delete_node_data(Object.keys(new_nodes));
this.delete_node_data(Object.keys(new_nodes))
// save the nodes and reactions again, for redo
new_nodes = utils.clone(saved_nodes);
new_nodes = utils.clone(saved_nodes)
// draw
this.clear_deleted_nodes();
this.clear_deleted_nodes()
// deselect
this.deselect_nodes();
this.deselect_nodes()
}.bind(this), function () {
// redo
// clone the nodes and reactions, to redo this action later
extend_and_draw_metabolite.apply(this, [new_nodes, selected_node_id]);
extend_and_draw_metabolite.apply(this, [new_nodes, selected_node_id])
// now redo the reaction
reaction_redo();
}.bind(this));
reaction_redo()
}.bind(this))

return null;
return

// definitions
function extend_and_draw_metabolite(new_nodes, selected_node_id) {
this.extend_nodes(new_nodes);
this.extend_nodes(new_nodes)
if (this.has_data_on_nodes) {
var scale_changed = this.apply_metabolite_data_to_nodes(new_nodes);
if (scale_changed) this.draw_all_nodes(false);
else this.draw_these_nodes([selected_node_id]);
var scale_changed = this.apply_metabolite_data_to_nodes(new_nodes)
if (scale_changed) this.draw_all_nodes(false)
else this.draw_these_nodes([selected_node_id])
} else {
this.draw_these_nodes([selected_node_id]);
this.draw_these_nodes([selected_node_id])
}
}
}
Expand Down Expand Up @@ -1490,32 +1497,18 @@ function extend_reactions(new_reactions) {
utils.extend(this.reactions, new_reactions);
}

/**
* Build a new reaction starting with selected_met. Undoable.
* @param {String} reaction_bigg_id - The BiGG ID of the reaction to draw.
* @param {String} selected_node_id - The ID of the node to begin drawing with.
* @param {Number} direction - The direction to draw in.
* @param {Boolean} [apply_undo_redo=true] - If true, then add to the undo
* stack. Otherwise, just return the undo and redo functions.
* @return An object of undo and redo functions:
* { undo: undo_function, redo: redo_function }
*/
function new_reaction_for_metabolite(reaction_bigg_id, selected_node_id,
direction, apply_undo_redo) {
/** Build a new reaction starting with selected_met.
Undoable
Arguments
---------
reaction_bigg_id: The BiGG ID of the reaction to draw.
selected_node_id: The ID of the node to begin drawing with.
direction: The direction to draw in.
apply_undo_redo: (Optional, Default: true) If true, then add to the
undo stack. Otherwise, just return the undo and redo functions.
Returns
-------
{ undo: undo_function,
redo: redo_function }
*/

// default args
if (apply_undo_redo === undefined) apply_undo_redo = true;

Expand Down
74 changes: 54 additions & 20 deletions js/src/tests/test_Map.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict'

const require_helper = require('./helpers/require_helper')

const Map = require_helper('Map')
const Settings = require_helper('Settings')
const CobraModel = require_helper('CobraModel')
Expand All @@ -14,6 +13,19 @@ const assert = require('chai').assert
const d3_body = require('./helpers/d3_body')
const get_map = require('./helpers/get_map')

const _ = require('underscore')

function matching_reaction (reactions, id) {
let match = null
for (let r_id in reactions) {
const r = reactions[r_id]
if (r.bigg_id === id) {
match = r
break
}
}
return match
}

describe('Map', () => {
let map, svg
Expand Down Expand Up @@ -194,37 +206,59 @@ describe('Map', () => {
{ type: 'text_label', text_label_id: id })
})

it('can accept new reactions', () => {
it('new_reaction_from_scratch', () => {
const model_data = { reactions: [ { id: 'acc_tpp',
metabolites: { acc_c: 1, acc_p: -1 },
gene_reaction_rule: 'Y1234'
}
],
metabolites: [ { id: 'acc_c',
formula: 'C3H2' },
{ id: 'acc_p',
formula: 'C3H2' }
metabolites: { acc_c: 1, acc_p: -1 },
gene_reaction_rule: 'Y1234'
}
],
genes: []
},
model = CobraModel.from_cobra_json(model_data)
metabolites: [ { id: 'acc_c',
formula: 'C3H2' },
{ id: 'acc_p',
formula: 'C3H2' }
],
genes: []
}
const model = CobraModel.from_cobra_json(model_data)
map.cobra_model = model

map.new_reaction_from_scratch('acc_tpp', {x: 0, y: 0, gene_reaction_rule: ''}, 0)
map.new_reaction_from_scratch('acc_tpp', { x: 0, y: 0 }, 0)

// find the reaction
let match = null
for (let r_id in map.reactions) {
const r = map.reactions[r_id]
if (r.bigg_id == 'acc_tpp')
match = r
}
const match = matching_reaction(map.reactions, 'acc_tpp')
assert.ok(match)
// gene reaction rule
assert.strictEqual(match.gene_reaction_rule,
model_data.reactions[0].gene_reaction_rule)
})

it('new_reaction_from_scratch exchanges', () => {
['uptake', 'secretion'].map(direction => {
const model_data = {
reactions: [{
id: 'EX_glc__D_e',
metabolites: { glc__D_e: direction === 'uptake' ? 1 : -1 },
gene_reaction_rule: ''
}],
metabolites: [{
id: 'glc__D_e',
formula: 'C6H12O6',
}],
genes: []
}
const model = CobraModel.from_cobra_json(model_data)
map.cobra_model = model

map.new_reaction_from_scratch('EX_glc__D_e', { x: 0, y: 0 }, 30)

// find the reaction
const match = matching_reaction(map.reactions, 'EX_glc__D_e')
assert.ok(match)
// segments
assert.strictEqual(_.size(match.segments), 3)
})
})

it('get_data_statistics accepts numbers or strings as floats; ignores empty strings and nulls', () => {
const data_reactions = { PGI: [10], GAPD: ['5'], TPI: [''], PGK: [null] }
map.apply_reaction_data_to_map(data_reactions)
Expand Down
10 changes: 10 additions & 0 deletions js/src/tests/test_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ describe('utils.array_to_object', () => {
})
})

describe('utils.clone', () => {
it('deep copies objects', () => {
const first = { a: 140, b: [ 'c', 'd' ] }
const second = utils.clone(first)
first.a += 1
assert.strictEqual(second.a, 140)
assert.notStrictEqual(first.b, second.b)
})
})

describe('utils.extend', () => {
it('adds attributes of second object to first', () => {
// extend
Expand Down
33 changes: 13 additions & 20 deletions js/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,26 +412,19 @@ function array_to_object(arr) {
return obj;
}

function clone(obj) {
// Handles the array and object types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
var copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
/**
* Deep copy for array and object types. All other types are returned by
* reference.
* @param {T<Object|Array|*>} obj - The object to copy.
* @return {T} The copied object.
*/
function clone (obj) {
if (_.isArray(obj))
return _.map(obj, function(t) { return clone(t) })
else if (_.isObject(obj))
return _.mapObject(obj, function (t, k) { return clone(t) })
else
return obj
}

function extend(obj1, obj2, overwrite) {
Expand Down

0 comments on commit 3e2bd91

Please sign in to comment.