UNPKG

tnt.board

Version:

TnT track-based board display

866 lines (736 loc) 21.7 kB
var apijs = require ("tnt.api"); var layout = require("./layout.js"); // FEATURE VIS // var board = {}; // board.track = {}; var tnt_feature = function () { var dispatch = d3.dispatch ("click", "dblclick", "mouseover", "mouseout"); ////// Vars exposed in the API var config = { create : function () {throw "create_elem is not defined in the base feature object";}, move : function () {throw "move_elem is not defined in the base feature object";}, distribute : function () {}, fixed : function () {}, //layout : function () {}, index : undefined, layout : layout.identity(), color : '#000', scale : undefined }; // The returned object var feature = {}; var reset = function () { var track = this; track.g.selectAll(".tnt_elem").remove(); track.g.selectAll(".tnt_guider").remove(); track.g.selectAll(".tnt_fixed").remove(); }; var init = function (width) { var track = this; track.g .append ("text") .attr ("class", "tnt_fixed") .attr ("x", 5) .attr ("y", 12) .attr ("font-size", 11) .attr ("fill", "grey") .text (track.label()); config.fixed.call(track, width); }; var plot = function (new_elems, track, xScale) { new_elems.on("click", function (d, i) { if (d3.event.defaultPrevented) { return; } dispatch.click.call(this, d, i); }); new_elems.on("mouseover", function (d, i) { if (d3.event.defaultPrevented) { return; } dispatch.mouseover.call(this, d, i); }); new_elems.on("dblclick", function (d, i) { if (d3.event.defaultPrevented) { return; } dispatch.dblclick.call(this, d, i); }); new_elems.on("mouseout", function (d, i) { if (d3.event.defaultPrevented) { return; } dispatch.mouseout.call(this, d, i); }); // new_elem is a g element the feature is inserted config.create.call(track, new_elems, xScale); }; var update = function (loc, field) { var track = this; var svg_g = track.g; var elements = track.data().elements(); if (field !== undefined) { elements = elements[field]; } var data_elems = config.layout.call(track, elements); if (data_elems === undefined) { return; } var vis_sel; var vis_elems; if (field !== undefined) { vis_sel = svg_g.selectAll(".tnt_elem_" + field); } else { vis_sel = svg_g.selectAll(".tnt_elem"); } if (config.index) { // Indexing by field vis_elems = vis_sel .data(data_elems, function (d) { if (d !== undefined) { return config.index(d); } }); } else { // Indexing by position in array vis_elems = vis_sel .data(data_elems); } config.distribute.call(track, vis_elems, config.scale); var new_elem = vis_elems .enter(); new_elem .append("g") .attr("class", "tnt_elem") .classed("tnt_elem_" + field, field) .call(feature.plot, track, config.scale); vis_elems .exit() .remove(); }; var mover = function (field) { var track = this; var svg_g = track.g; var elems; // TODO: Is selecting the elements to move too slow? // It would be nice to profile if (field !== undefined) { elems = svg_g.selectAll(".tnt_elem_" + field); } else { elems = svg_g.selectAll(".tnt_elem"); } config.move.call(this, elems); }; var mtf = function (elem) { elem.parentNode.appendChild(elem); }; var move_to_front = function (field) { if (field !== undefined) { var track = this; var svg_g = track.g; svg_g.selectAll(".tnt_elem_" + field) .each( function () { mtf(this); }); } }; // API apijs (feature) .getset (config) .method ({ reset : reset, plot : plot, update : update, mover : mover, init : init, move_to_front : move_to_front }); return d3.rebind(feature, dispatch, "on"); }; tnt_feature.composite = function () { var displays = {}; var display_order = []; var features = {}; var reset = function () { var track = this; for (var display in displays) { if (displays.hasOwnProperty(display)) { displays[display].reset.call(track); } } }; var init = function (width) { var track = this; for (var display in displays) { if (displays.hasOwnProperty(display)) { displays[display].scale(features.scale()); displays[display].init.call(track, width); } } }; var update = function () { var track = this; for (var i=0; i<display_order.length; i++) { displays[display_order[i]].update.call(track, undefined, display_order[i]); displays[display_order[i]].move_to_front.call(track, display_order[i]); } // for (var display in displays) { // if (displays.hasOwnProperty(display)) { // displays[display].update.call(track, xScale, display); // } // } }; var mover = function () { var track = this; for (var display in displays) { if (displays.hasOwnProperty(display)) { displays[display].mover.call(track, display); } } }; var add = function (key, display) { displays[key] = display; display_order.push(key); return features; }; var get_displays = function () { var ds = []; for (var i=0; i<display_order.length; i++) { ds.push(displays[display_order[i]]); } return ds; }; // API apijs (features) .getset("scale") .method ({ reset : reset, update : update, mover : mover, init : init, add : add, displays : get_displays }); return features; }; tnt_feature.area = function () { var feature = tnt_feature.line(); var line = feature.line(); var area = d3.svg.area() .interpolate(line.interpolate()) .tension(feature.tension()); var data_points; var line_create = feature.create(); // We 'save' line creation feature.create (function (points) { var track = this; var xScale = feature.scale(); if (data_points !== undefined) { track.g.select("path").remove(); } line_create.call(track, points, xScale); area .x(line.x()) .y1(line.y()) .y0(track.height()); data_points = points.data(); points.remove(); track.g .append("path") .attr("class", "tnt_area") .classed("tnt_elem", true) .datum(data_points) .attr("d", area) .attr("fill", d3.rgb(feature.color()).brighter()); }); var line_move = feature.move(); feature.move (function (path) { var track = this; var xScale = feature.scale(); line_move.call(track, path, xScale); area.x(line.x()); track.g .select(".tnt_area") .datum(data_points) .attr("d", area); }); return feature; }; tnt_feature.line = function () { var feature = tnt_feature(); var x = function (d) { return d.pos; }; var y = function (d) { return d.val; }; var tension = 0.7; var yScale = d3.scale.linear(); var line = d3.svg.line() .interpolate("basis"); // line getter. TODO: Setter? feature.line = function () { return line; }; feature.x = function (cbak) { if (!arguments.length) { return x; } x = cbak; return feature; }; feature.y = function (cbak) { if (!arguments.length) { return y; } y = cbak; return feature; }; feature.tension = function (t) { if (!arguments.length) { return tension; } tension = t; return feature; }; var data_points; // For now, create is a one-off event // TODO: Make it work with partial paths, ie. creating and displaying only the path that is being displayed feature.create (function (points) { var track = this; var xScale = feature.scale(); if (data_points !== undefined) { // return; track.g.select("path").remove(); } line .tension(tension) .x(function (d) { return xScale(x(d)); }) .y(function (d) { return track.height() - yScale(y(d)); }); data_points = points.data(); points.remove(); yScale .domain([0, 1]) // .domain([0, d3.max(data_points, function (d) { // return y(d); // })]) .range([0, track.height() - 2]); track.g .append("path") .attr("class", "tnt_elem") .attr("d", line(data_points)) .style("stroke", feature.color()) .style("stroke-width", 4) .style("fill", "none"); }); feature.move (function (path) { var track = this; var xScale = feature.scale(); line.x(function (d) { return xScale(x(d)); }); track.g.select("path") .attr("d", line(data_points)); }); return feature; }; tnt_feature.conservation = function () { // 'Inherit' from feature.area var feature = tnt_feature.area(); var area_create = feature.create(); // We 'save' area creation feature.create (function (points) { var track = this; var xScale = feature.scale(); area_create.call(track, d3.select(points[0][0]), xScale); }); return feature; }; tnt_feature.ensembl = function () { // 'Inherit' from board.track.feature var feature = tnt_feature(); var color2 = "#7FFF00"; var color3 = "#00BB00"; feature.fixed (function (width) { var track = this; var height_offset = ~~(track.height() - (track.height() * 0.8)) / 2; track.g .append("line") .attr("class", "tnt_guider tnt_fixed") .attr("x1", 0) .attr("x2", width) .attr("y1", height_offset) .attr("y2", height_offset) .style("stroke", feature.color()) .style("stroke-width", 1); track.g .append("line") .attr("class", "tnt_guider tnt_fixed") .attr("x1", 0) .attr("x2", width) .attr("y1", track.height() - height_offset) .attr("y2", track.height() - height_offset) .style("stroke", feature.color()) .style("stroke-width", 1); }); feature.create (function (new_elems) { var track = this; var xScale = feature.scale(); var height_offset = ~~(track.height() - (track.height() * 0.8)) / 2; new_elems .append("rect") .attr("x", function (d) { return xScale (d.start); }) .attr("y", height_offset) // .attr("rx", 3) // .attr("ry", 3) .attr("width", function (d) { return (xScale(d.end) - xScale(d.start)); }) .attr("height", track.height() - ~~(height_offset * 2)) .attr("fill", track.color()) .transition() .duration(500) .attr("fill", function (d) { if (d.type === 'high') { return d3.rgb(feature.color()); } if (d.type === 'low') { return d3.rgb(feature.color2()); } return d3.rgb(feature.color3()); }); }); feature.distribute (function (blocks) { var xScale = feature.scale(); blocks .select("rect") .attr("width", function (d) { return (xScale(d.end) - xScale(d.start)); }); }); feature.move (function (blocks) { var xScale = feature.scale(); blocks .select("rect") .attr("x", function (d) { return xScale(d.start); }) .attr("width", function (d) { return (xScale(d.end) - xScale(d.start)); }); }); feature.color2 = function (col) { if (!arguments.length) { return color2; } color2 = col; return feature; }; feature.color3 = function (col) { if (!arguments.length) { return color3; } color3 = col; return feature; }; return feature; }; tnt_feature.vline = function () { // 'Inherit' from feature var feature = tnt_feature(); feature.create (function (new_elems) { var xScale = feature.scale(); var track = this; new_elems .append ("line") .attr("x1", function (d) { return xScale(feature.index()(d)); }) .attr("x2", function (d) { return xScale(feature.index()(d)); }) .attr("y1", 0) .attr("y2", track.height()) .attr("stroke", feature.color()) .attr("stroke-width", 1); }); feature.move (function (vlines) { var xScale = feature.scale(); vlines .select("line") .attr("x1", function (d) { return xScale(feature.index()(d)); }) .attr("x2", function (d) { return xScale(feature.index()(d)); }); }); return feature; }; tnt_feature.pin = function () { // 'Inherit' from board.track.feature var feature = tnt_feature(); var yScale = d3.scale.linear() .domain([0,0]) .range([0,0]); var opts = { pos : d3.functor("pos"), val : d3.functor("val"), domain : [0,1] }; var pin_ball_r = 5; // the radius of the circle in the pin apijs(feature) .getset(opts); feature.create (function (new_pins) { var track = this; var xScale = feature.scale(); yScale .domain(feature.domain()) .range([pin_ball_r, track.height()-pin_ball_r-10]); // 10 for labelling // pins are composed of lines, circles and labels new_pins .append("line") .attr("x1", function (d, i) { return xScale(d[opts.pos(d, i)]); }) .attr("y1", function (d) { return track.height(); }) .attr("x2", function (d,i) { return xScale(d[opts.pos(d, i)]); }) .attr("y2", function (d, i) { return track.height() - yScale(d[opts.val(d, i)]); }) .attr("stroke", function (d) { return d3.functor(feature.color())(d); }); new_pins .append("circle") .attr("cx", function (d, i) { return xScale(d[opts.pos(d, i)]); }) .attr("cy", function (d, i) { return track.height() - yScale(d[opts.val(d, i)]); }) .attr("r", pin_ball_r) .attr("fill", function (d) { return d3.functor(feature.color())(d); }); new_pins .append("text") .attr("font-size", "13") .attr("x", function (d, i) { return xScale(d[opts.pos(d, i)]); }) .attr("y", function (d, i) { return 10; }) .style("text-anchor", "middle") .style("fill", function (d) { return d3.functor(feature.color())(d); }) .text(function (d) { return d.label || ""; }); }); feature.distribute (function (pins) { pins .select("text") .text(function (d) { return d.label || ""; }); }); feature.move(function (pins) { var track = this; var xScale = feature.scale(); pins //.each(position_pin_line) .select("line") .attr("x1", function (d, i) { return xScale(d[opts.pos(d, i)]); }) .attr("y1", function (d) { return track.height(); }) .attr("x2", function (d,i) { return xScale(d[opts.pos(d, i)]); }) .attr("y2", function (d, i) { return track.height() - yScale(d[opts.val(d, i)]); }); pins .select("circle") .attr("cx", function (d, i) { return xScale(d[opts.pos(d, i)]); }) .attr("cy", function (d, i) { return track.height() - yScale(d[opts.val(d, i)]); }); pins .select("text") .attr("x", function (d, i) { return xScale(d[opts.pos(d, i)]); }) .text(function (d) { return d.label || ""; }); }); feature.fixed (function (width) { var track = this; track.g .append("line") .attr("class", "tnt_fixed") .attr("x1", 0) .attr("x2", width) .attr("y1", track.height()) .attr("y2", track.height()) .style("stroke", "black") .style("stroke-with", "1px"); }); return feature; }; tnt_feature.block = function () { // 'Inherit' from board.track.feature var feature = tnt_feature(); apijs(feature) .getset('from', function (d) { return d.start; }) .getset('to', function (d) { return d.end; }); feature.create(function (new_elems) { var track = this; var xScale = feature.scale(); new_elems .append("rect") .attr("x", function (d, i) { // TODO: start, end should be adjustable via the tracks API return xScale(feature.from()(d, i)); }) .attr("y", 0) .attr("width", function (d, i) { return (xScale(feature.to()(d, i)) - xScale(feature.from()(d, i))); }) .attr("height", track.height()) .attr("fill", track.color()) .transition() .duration(500) .attr("fill", function (d) { if (d.color === undefined) { return feature.color(); } else { return d.color; } }); }); feature.distribute(function (elems) { var xScale = feature.scale(); elems .select("rect") .attr("width", function (d) { return (xScale(d.end) - xScale(d.start)); }); }); feature.move(function (blocks) { var xScale = feature.scale(); blocks .select("rect") .attr("x", function (d) { return xScale(d.start); }) .attr("width", function (d) { return (xScale(d.end) - xScale(d.start)); }); }); return feature; }; tnt_feature.axis = function () { var xAxis; var orientation = "top"; var xScale; // Axis doesn't inherit from feature var feature = {}; feature.reset = function () { xAxis = undefined; var track = this; track.g.selectAll(".tick").remove(); }; feature.plot = function () {}; feature.mover = function () { var track = this; var svg_g = track.g; svg_g.call(xAxis); }; feature.init = function () { xAxis = undefined; }; feature.update = function () { // Create Axis if it doesn't exist if (xAxis === undefined) { xAxis = d3.svg.axis() .scale(xScale) .orient(orientation); } var track = this; var svg_g = track.g; svg_g.call(xAxis); }; feature.orientation = function (pos) { if (!arguments.length) { return orientation; } orientation = pos; return this; }; feature.scale = function (s) { if (!arguments.length) { return xScale; } xScale = s; return this; }; return feature; }; tnt_feature.location = function () { var row; var xScale; var feature = {}; feature.reset = function () { row = undefined; }; feature.plot = function () {}; feature.init = function () { row = undefined; var track = this; track.g.select("text").remove(); }; feature.mover = function() { var domain = xScale.domain(); row.select("text") .text("Location: " + ~~domain[0] + "-" + ~~domain[1]); }; feature.scale = function (sc) { if (!arguments.length) { return xScale; } xScale = sc; return this; }; feature.update = function (loc) { var track = this; var svg_g = track.g; var domain = xScale.domain(); if (row === undefined) { row = svg_g; row .append("text") .text("Location: " + Math.round(domain[0]) + "-" + Math.round(domain[1])); } }; return feature; }; module.exports = exports = tnt_feature;