Skip to content

Instantly share code, notes, and snippets.

@florin-chelaru
Last active August 29, 2015 14:00
Show Gist options
  • Save florin-chelaru/11279449 to your computer and use it in GitHub Desktop.
Save florin-chelaru/11279449 to your computer and use it in GitHub Desktop.
Exon Track
epiviz.EpiViz.SETTINGS.chartTypes.push('epiviz.plugins.charts.ExonTrackType');
/**
* Created by Florin Chelaru ( florinc [at] umd [dot] edu )
* Date: 4/25/14
* Time: 12:36 AM
*/
goog.provide('epiviz.plugins.charts.ExonTrackType');
goog.require('epiviz.ui.charts.Chart');
/**
* @param {epiviz.Config} config
* @extends {epiviz.ui.charts.TrackType}
* @constructor
*/
epiviz.plugins.charts.ExonTrackType = function(config) {
// Call superclass constructor
epiviz.ui.charts.TrackType.call(this, config);
};
/*
* Copy methods from upper class
*/
epiviz.plugins.charts.ExonTrackType.prototype = epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);
epiviz.plugins.charts.ExonTrackType.constructor = epiviz.plugins.charts.ExonTrackType;
/**
* @param {string} id
* @param {jQuery} container The div where the chart will be drawn
* @param {epiviz.ui.charts.ChartProperties} properties
* @returns {epiviz.plugins.charts.ExonTrack}
*/
epiviz.plugins.charts.ExonTrackType.prototype.createNew = function(id, container, properties) {
return new epiviz.plugins.charts.ExonTrack(id, container, properties);
};
/**
* @returns {string}
*/
epiviz.plugins.charts.ExonTrackType.prototype.typeName = function() {
return 'epiviz.plugins.charts.ExonTrack';
};
/**
* @returns {string}
*/
epiviz.plugins.charts.ExonTrackType.prototype.chartName = function() {
return 'Exon Track';
};
/**
* @returns {string}
*/
epiviz.plugins.charts.ExonTrackType.prototype.chartHtmlAttributeName = function() {
return 'exons';
};
/**
* @returns {epiviz.measurements.Measurement.Type}
*/
epiviz.plugins.charts.ExonTrackType.prototype.chartContentType = function() {
return epiviz.measurements.Measurement.Type.FEATURE;
};
/**
* @returns {Array.<epiviz.ui.charts.CustomSetting>}
*/
epiviz.plugins.charts.ExonTrackType.prototype.customSettingsDefs = function() {
return epiviz.ui.charts.TrackType.prototype.customSettingsDefs.call(this).concat([
new epiviz.ui.charts.CustomSetting(
epiviz.plugins.charts.ExonTrackType.CustomSettings.GROUP_LABEL,
epiviz.ui.charts.CustomSetting.Type.STRING,
'symbol',
'Group label'),
new epiviz.ui.charts.CustomSetting(
epiviz.plugins.charts.ExonTrackType.CustomSettings.SHOW_POINTS,
epiviz.ui.charts.CustomSetting.Type.BOOLEAN,
false,
'Show points'),
new epiviz.ui.charts.CustomSetting(
epiviz.plugins.charts.ExonTrackType.CustomSettings.SHOW_LINES,
epiviz.ui.charts.CustomSetting.Type.BOOLEAN,
true,
'Show lines'),
new epiviz.ui.charts.CustomSetting(
epiviz.plugins.charts.ExonTrackType.CustomSettings.POINT_RADIUS,
epiviz.ui.charts.CustomSetting.Type.NUMBER,
1,
'Point radius'),
new epiviz.ui.charts.CustomSetting(
epiviz.plugins.charts.ExonTrackType.CustomSettings.LINE_THICKNESS,
epiviz.ui.charts.CustomSetting.Type.NUMBER,
1,
'Line thickness'),
new epiviz.ui.charts.CustomSetting(
epiviz.ui.charts.ChartType.CustomSettings.Y_MIN,
epiviz.ui.charts.CustomSetting.Type.NUMBER,
epiviz.ui.charts.CustomSetting.DEFAULT,
'Min Y'),
new epiviz.ui.charts.CustomSetting(
epiviz.ui.charts.ChartType.CustomSettings.Y_MAX,
epiviz.ui.charts.CustomSetting.Type.NUMBER,
epiviz.ui.charts.CustomSetting.DEFAULT,
'Max Y'),
new epiviz.ui.charts.CustomSetting(
epiviz.plugins.charts.ExonTrackType.CustomSettings.INTERPOLATION,
epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,
'step-after',
'Interpolation',
['linear', 'step-before', 'step-after', 'basis', 'basis-open', 'basis-closed', 'bundle', 'cardinal', 'cardinal-open', 'monotone'])
]);
};
/**
* @enum {string}
*/
epiviz.plugins.charts.ExonTrackType.CustomSettings = {
GROUP_LABEL: 'groupLabel',
SHOW_POINTS: 'showPoints',
SHOW_LINES: 'showLines',
POINT_RADIUS: 'pointRadius',
LINE_THICKNESS: 'lineThickness',
INTERPOLATION: 'interpolation'
};
/**
* Created by Florin Chelaru ( florinc [at] umd [dot] edu )
* Date: 4/25/14
* Time: 12:34 AM
*/
goog.provide('epiviz.plugins.charts.ExonTrack');
/**
* @param {string} id
* @param {jQuery} container
* @param {epiviz.ui.charts.ChartProperties} properties
* @extends {epiviz.ui.charts.Track}
* @constructor
*/
epiviz.plugins.charts.ExonTrack = function(id, container, properties) {
// Call superclass constructor
epiviz.ui.charts.Track.call(this, id, container, properties);
this._initialize();
};
/*
* Copy methods from upper class
*/
epiviz.plugins.charts.ExonTrack.prototype = epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);
epiviz.plugins.charts.ExonTrack.constructor = epiviz.plugins.charts.ExonTrack;
/**
* @protected
*/
epiviz.plugins.charts.ExonTrack.prototype._initialize = function() {
// Call super
epiviz.ui.charts.Track.prototype._initialize.call(this);
};
/**
* @param {epiviz.datatypes.GenomicRange} [range]
* @param {?epiviz.measurements.MeasurementHashtable.<epiviz.datatypes.GenomicDataMeasurementWrapper>} [data]
* @param {number} [slide]
* @param {number} [zoom]
* @returns {Array.<epiviz.ui.charts.UiObject>} The objects drawn
*/
epiviz.plugins.charts.ExonTrack.prototype.draw = function(range, data, slide, zoom) {
var lastRange = this._lastRange;
epiviz.ui.charts.Track.prototype.draw.call(this, range, data, slide, zoom);
// If data is defined, then the base class sets this._lastData to data.
// If it isn't, then we'll use the data from the last draw call
data = this._lastData;
range = this._lastRange;
if (lastRange && range && lastRange.overlapsWith(range) && lastRange.width() == range.width()) {
slide = range.start() - lastRange.start();
}
// If data is not defined, there is nothing to draw
if (!data || !range) { return []; }
var CustomSetting = epiviz.ui.charts.CustomSetting;
var minY = this._customSettingsValues[epiviz.ui.charts.ChartType.CustomSettings.Y_MIN];
var maxY = this._customSettingsValues[epiviz.ui.charts.ChartType.CustomSettings.Y_MAX];
if (minY == CustomSetting.DEFAULT) {
minY = null;
this.measurements().foreach(function(m) {
if (m === null) { return; }
if (minY === null || m.minValue() < minY) { minY = m.minValue(); }
});
}
if (maxY == CustomSetting.DEFAULT) {
maxY = null;
this.measurements().foreach(function(m) {
if (m === null) { return; }
if (maxY === null || m.maxValue() > maxY) { maxY = m.maxValue(); }
});
}
if (minY === null && maxY === null) { minY = -1; maxY = 1; }
if (minY === null) { minY = maxY - 1; }
if (maxY === null) { maxY = minY + 1; }
var Axis = epiviz.ui.charts.Axis;
var xScale = d3.scale.linear()
.domain([range.start(), range.end()])
.range([0, this.width() - this.margins().sumAxis(Axis.X)]);
var yScale = d3.scale.linear()
.domain([minY, maxY])
.range([this.height() - this.margins().sumAxis(Axis.Y), 0]);
this._clearAxes();
this._drawAxes(xScale, yScale, 10, 5);
slide = slide || 0;
var delta = slide * (this.width() - this.margins().sumAxis(Axis.X)) / range.width();
var linesGroup = this._svg.selectAll('.lines');
if (linesGroup.empty()) {
var graph = this._svg.append('g')
.attr('class', 'lines')
.attr('transform', 'translate(' + this.margins().left() + ', ' + this.margins().top() + ')');
this.measurements().foreach(function(m, i) {
graph.append('g').attr('class', 'line-series-index-' + i);
graph.append('g').attr('class', 'point-series-index-' + i);
});
}
return this._drawLines(range, data, delta, zoom || 1, xScale, yScale);
};
/**
* @param {epiviz.datatypes.GenomicRange} range
* @param {epiviz.measurements.MeasurementHashtable.<epiviz.datatypes.GenomicDataMeasurementWrapper>} data
* @param {number} delta
* @param {number} zoom
* @param {function} xScale D3 linear scale
* @param {function} yScale D3 linear scale
* @returns {Array.<epiviz.ui.charts.UiObject>} The objects drawn
* @private
*/
epiviz.plugins.charts.ExonTrack.prototype._drawLines = function(range, data, delta, zoom, xScale, yScale) {
var self = this;
var invXScale = d3.scale.linear()
.domain([0, this.width() - this.margins().sumAxis(epiviz.ui.charts.Axis.X)])
.range([range.start(), range.end()]);
var deltaInBp = invXScale(delta) - range.start();
var extendedRange = epiviz.datatypes.GenomicRange.fromStartEnd(
range.seqName(),
Math.min(range.start(), range.start() + deltaInBp),
Math.max(range.end(), range.end() + deltaInBp));
var graph = this._svg.select('.lines');
/** @type {Array.<epiviz.ui.charts.UiObject>} */
var items = [];
/** @type {string} */
var groupLabel = this._customSettingsValues[epiviz.plugins.charts.ExonTrackType.CustomSettings.GROUP_LABEL];
this.measurements().foreach(function(m, i) {
/** @type {epiviz.datatypes.GenomicDataMeasurementWrapper} */
var series = data.get(m);
/** @type {{index: ?number, length: number}} */
var drawBoundaries = series.binarySearchStarts(extendedRange);
if (drawBoundaries.length == 0) { return; }
// Also take the last point that won't be displayed on the left side
if (drawBoundaries.index > 0) {
--drawBoundaries.index;
++drawBoundaries.length;
}
// And the first point on the right side that won't be displayed
if (drawBoundaries.index + drawBoundaries.length < series.size()) {
++drawBoundaries.length;
}
var indices = epiviz.utils.range(drawBoundaries.length, drawBoundaries.index);
var itemIndicesMap = {};
for (var k = 0; k < indices.length; ++k) {
var cell = series.get(indices[k]);
var group = cell.rowItem.metadata(groupLabel);
if (group == undefined) { continue; }
var o;
if (itemIndicesMap[group] == undefined) {
o = new epiviz.ui.charts.UiObject(sprintf('line_%s_%s', i, group), cell.rowItem.start(), cell.rowItem.end(), [cell.value], i, [[cell]], [m], sprintf('item data-series-%s', i));
itemIndicesMap[group] = items.length;
items.push(o);
continue;
}
o = items[itemIndicesMap[group]];
o.start = Math.min(o.start, cell.rowItem.start());
o.end = Math.max(o.end, cell.rowItem.end());
o.values[0] = (o.values[0] * o.valueItems[0].length + cell.value) / (o.valueItems[0].length + 1);
o.valueItems[0].push(cell);
}
var itemsGroup = graph.select('.line-series-index-' + i);
var selection = itemsGroup.selectAll('.item')
.data(items, function(d) { return d.id; });
selection
.enter()
.append('g')
.on('mouseout', function (d) {
self._unhover.notify();
})
.on('mouseover', function (d) {
self._hover.notify(d);
})
.on('click', function(d) {
self._deselect.notify();
self._select.notify(d);
d3.event.stopPropagation();
});
selection
.each(function(d) {
self._drawGroup(this, d, xScale, yScale);
})
.attr('transform', 'translate(' + (+delta) + ')')
.transition()
.duration(500)
.attr('transform', 'translate(' + (0) + ')');
selection
.exit()
.remove();
});
return items;
};
/**
* @param elem
* @param {epiviz.ui.charts.UiObject} d
* @param {function(number): number} xScale
* @param {function(number): number} yScale
* @private
*/
epiviz.plugins.charts.ExonTrack.prototype._drawGroup = function(elem, d, xScale, yScale) {
var interpolation = this._customSettingsValues[epiviz.plugins.charts.ExonTrackType.CustomSettings.INTERPOLATION];
/** @type {number} */
var lineThickness = this._customSettingsValues[epiviz.plugins.charts.ExonTrackType.CustomSettings.LINE_THICKNESS];
/** @type {number} */
var pointRadius = this._customSettingsValues[epiviz.plugins.charts.ExonTrackType.CustomSettings.POINT_RADIUS];
/** @type {boolean} */
var showPoints = this._customSettingsValues[epiviz.plugins.charts.ExonTrackType.CustomSettings.SHOW_POINTS];
/** @type {boolean} */
var showLines = this._customSettingsValues[epiviz.plugins.charts.ExonTrackType.CustomSettings.SHOW_LINES];
var group = d3.select(elem);
group.attr('class', d.cssClasses);
var x = function(i) {
var index = Math.floor(i / 2);
if (i % 2 == 0) { return xScale(d.valueItems[0][index].rowItem.start()); }
else { return xScale(d.valueItems[0][index].rowItem.end());}
};
var y = function(i) {
var index = Math.floor(i / 2);
return yScale(d.valueItems[0][index].value);
};
var line = d3.svg.line()
.x(x).y(y)
.interpolate(interpolation);
if (showLines) {
var lineSelection = group
.selectAll('path')
.data([epiviz.utils.range(d.valueItems[0].length * 2)]);
lineSelection
.enter()
.append('path')
.style('shape-rendering', 'auto')
.style('stroke-opacity', '0.7');
lineSelection
.attr('d', line)
.style('stroke', this.colors().get(d.seriesIndex))
.style('stroke-width', lineThickness);
} else {
group.selectAll('path').remove();
}
if (showPoints) {
var pointSelection = group
.selectAll('circle')
.data(epiviz.utils.range(d.valueItems[0].length * 2));
pointSelection
.enter()
.append('circle');
pointSelection
.attr('r', pointRadius)
.attr('cx', x)
.attr('cy', y)
.attr('fill', this.colors().get(d.seriesIndex))
.attr('fill-opacity', 0.1)
.attr('stroke', this.colors().get(d.seriesIndex));
} else {
group.selectAll('circle').remove();
}
};
/**
* @returns {string}
*/
epiviz.plugins.charts.ExonTrack.prototype.chartTypeName = function() { return 'epiviz.plugins.charts.ExonTrack'; };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment