Last active
August 29, 2015 13:58
-
-
Save florin-chelaru/9983638 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Florin Chelaru ( florinc [at] umd [dot] edu ) | |
* Date: 11/14/13 | |
* Time: 11:55 PM | |
*/ | |
// goog.provide('epiviz.plugins.charts.ScatterPlot'); | |
/** | |
* @param {string} id | |
* @param {jQuery} container | |
* @param {epiviz.ui.charts.ChartProperties} properties | |
* @extends {epiviz.ui.charts.Plot} | |
* @constructor | |
*/ | |
epiviz.plugins.charts.ScatterPlot = function(id, container, properties) { | |
// Call superclass constructor | |
epiviz.ui.charts.Plot.call(this, id, container, properties); | |
/** | |
* @type {*} D3 chart container | |
* @private | |
*/ | |
this._chartContent = null; | |
/** | |
* @type {jQuery} | |
* @private | |
*/ | |
this._jChartContent = null; | |
/** | |
* @type {*} D3 legend container | |
* @private | |
*/ | |
this._legend = null; | |
/** | |
* @type {Array.<epiviz.measurements.Measurement>} | |
* @private | |
*/ | |
this._measurementsX = []; | |
/** | |
* @type {Array.<epiviz.measurements.Measurement>} | |
* @private | |
*/ | |
this._measurementsY = []; | |
var self = this; | |
this.measurements().foreach(function(m, i) { | |
if (i % 2 == 0) { self._measurementsX.push(m); } | |
else { self._measurementsY.push(m); } | |
}); | |
/** | |
* @type {string} | |
* @private | |
*/ | |
this._xLabel = ''; | |
/** | |
* @type {string} | |
* @private | |
*/ | |
this._yLabel = ''; | |
for (var i = 0; i < Math.min(this._measurementsX.length, this._measurementsY.length); ++i) { | |
if (i > 0) { | |
this._xLabel += ', '; | |
this._yLabel += ', '; | |
} | |
this._xLabel += this._measurementsX[i].name(); | |
this._yLabel += this._measurementsY[i].name(); | |
} | |
this._initialize(); | |
}; | |
/* | |
* Copy methods from upper class | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype = epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype); | |
epiviz.plugins.charts.ScatterPlot.constructor = epiviz.plugins.charts.ScatterPlot; | |
/** | |
* @protected | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype._initialize = function() { | |
// Call super | |
epiviz.ui.charts.Plot.prototype._initialize.call(this); | |
this._chartContent = this._svg.append('g').attr('class', 'chart-content'); | |
this._jChartContent = this._container.find('.chart-content'); | |
this._legend = this._svg.append('g').attr('class', 'chart-legend'); | |
}; | |
/** | |
* @protected | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype._addStyles = function() { | |
var svgId = '#' + this._svgId; | |
var style = | |
sprintf('%s .items {}\n', svgId) + | |
sprintf('%s .items .selected { fill: #1a6d00; stroke: #ffc600; stroke-width: 2; stroke-opacity: 1.0; opacity: 1; }\n', svgId) + | |
sprintf('%s .items .hovered { fill: #1a6d00; stroke: #ffc600; stroke-width: 2; stroke-opacity: 1.0; opacity: 1; }\n', svgId); | |
var nSeries = Math.min(this._measurementsX.length, this._measurementsY.length); | |
for (var i = 0; i < nSeries; ++i) { | |
style += sprintf('%s .data-series-%s { opacity: 1.0; }\n', svgId, i); | |
} | |
var jSvg = this._container.find('svg'); | |
jSvg.append(sprintf('<style type="text/css">%s</style>', style)); | |
}; | |
/** | |
* @param {epiviz.datatypes.GenomicRange} [range] | |
* @param {?epiviz.measurements.MeasurementHashtable.<epiviz.datatypes.GenomicDataMeasurementWrapper>} [data] | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype.draw = function(range, data) { | |
epiviz.ui.charts.Plot.prototype.draw.call(this, range, data); | |
// 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 data is not defined, there is nothing to draw | |
if (!data || !range) { return; } | |
this._svg | |
.attr('width', this._properties.width) | |
.attr('height', this._properties.height); | |
this._drawCircles(range, data); | |
// TODO: Legend | |
//this._drawLegend(); | |
}; | |
/** | |
* @param {epiviz.datatypes.GenomicRange} range | |
* @param {epiviz.measurements.MeasurementHashtable.<epiviz.datatypes.GenomicDataMeasurementWrapper>} data | |
* @private | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype._drawCircles = function(range, data) { | |
var self = this; | |
var Axis = epiviz.ui.charts.Axis; | |
var circleRadius = this._customSettingsValues[epiviz.plugins.charts.ScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO] * Math.min(this.width(), this.height()); | |
var gridSquareSize = Math.floor(circleRadius); | |
var nSeries = Math.min(this._measurementsX.length, this._measurementsY.length); | |
var firstGlobalIndex = data.first().value.globalStartIndex(); | |
var lastGlobalIndex = data.first().value.size() + firstGlobalIndex; | |
data.foreach(function(measurement, series) { | |
var firstIndex = series.globalStartIndex(); | |
var lastIndex = series.size() + firstIndex; | |
if (firstIndex > firstGlobalIndex) { firstGlobalIndex = firstIndex; } | |
if (lastIndex < lastGlobalIndex) { lastGlobalIndex = lastIndex; } | |
}); | |
var nGenes = lastGlobalIndex - firstGlobalIndex; | |
var margins = this.margins(); | |
var width = this.width(); | |
var height = this.height(); | |
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]; | |
var minX = this._customSettingsValues[epiviz.ui.charts.ChartType.CustomSettings.X_MIN]; | |
var maxX = this._customSettingsValues[epiviz.ui.charts.ChartType.CustomSettings.X_MAX]; | |
if (minX == CustomSetting.DEFAULT) { minX = this._measurementsX[0].minValue(); } | |
if (minY == CustomSetting.DEFAULT) { minY = this._measurementsY[0].minValue(); } | |
if (maxX == CustomSetting.DEFAULT) { maxX = this._measurementsX[0].maxValue(); } | |
if (maxY == CustomSetting.DEFAULT) { maxY = this._measurementsY[0].maxValue(); } | |
var xScale = d3.scale.linear() | |
.domain([minX, maxX]) | |
.range([0, width - margins.sumAxis(Axis.X)]); | |
var yScale = d3.scale.linear() | |
.domain([minY, maxY]) | |
.range([height - margins.sumAxis(Axis.Y), 0]); | |
this._clearAxes(this._chartContent); | |
this._drawAxes(xScale, yScale, 15, 15, this._chartContent); | |
var i, index; | |
var indices = []; //epiviz.utils.range(nSeries * nGenes); | |
for (i = 0; i < nGenes; ++i) { | |
index = i + firstGlobalIndex; | |
var item = data.get(this._measurementsX[0]).getByGlobalIndex(index).rowItem; | |
if (item.start() < range.end() && item.end() > range.start()) { | |
for (var j = 0; j < nSeries; ++j) { | |
indices.push(j * nGenes + i); | |
} | |
} | |
} | |
var grid = {}; | |
var items = []; | |
var maxGroupItems = 1; | |
for (i = 0; i < indices.length; ++i) { | |
index = indices[i] % nGenes; | |
var globalIndex = index + firstGlobalIndex; | |
var seriesIndex = Math.floor(index / nGenes); | |
var mX = self._measurementsX[seriesIndex]; | |
var mY = self._measurementsY[seriesIndex]; | |
var cellX = data.get(mX).getByGlobalIndex(globalIndex); | |
var cellY = data.get(mY).getByGlobalIndex(globalIndex); | |
var classes = sprintf('item data-series-%s', seriesIndex); | |
var x = xScale(cellX.value); | |
var y = yScale(cellY.value); | |
var gridX = Math.floor(x / gridSquareSize) * gridSquareSize; | |
var gridY = Math.floor(y / gridSquareSize) * gridSquareSize; | |
var uiObj = null; | |
if (grid[gridY] && grid[gridY][gridX]) { | |
uiObj = grid[gridY][gridX]; | |
uiObj.id += '_' + cellX.globalIndex; | |
uiObj.start = Math.min(uiObj.start, cellX.rowItem.start()); | |
uiObj.end = Math.max(uiObj.end, cellX.rowItem.end()); | |
uiObj.values[0] = (uiObj.values[0] * uiObj.valueItems[0].length + cellX.value) / (uiObj.valueItems[0].length + 1); | |
uiObj.values[1] = (uiObj.values[1] * uiObj.valueItems[1].length + cellY.value) / (uiObj.valueItems[1].length + 1); | |
uiObj.valueItems[0].push(cellX); | |
uiObj.valueItems[1].push(cellY); | |
if (uiObj.valueItems[0].length > maxGroupItems) { | |
maxGroupItems = uiObj.valueItems[0].length; | |
} | |
continue; | |
} | |
uiObj = new epiviz.ui.charts.UiObject( | |
sprintf('scatter_%s_%s', seriesIndex, cellX.globalIndex), | |
cellX.rowItem.start(), | |
cellX.rowItem.end(), | |
[cellX.value, cellY.value], | |
seriesIndex, | |
[[cellX], [cellY]], // valueItems one for each measurement | |
[mX, mY], // measurements | |
classes); | |
if (!grid[gridY]) { grid[gridY] = {}; } | |
grid[gridY][gridX] = uiObj; | |
items.push(uiObj); | |
} | |
var itemsGroup = this._chartContent.select('.items'); | |
if (itemsGroup.empty()) { | |
itemsGroup = this._chartContent.append('g').attr('class', 'items'); | |
var selectedGroup = itemsGroup.append('g').attr('class', 'selected'); | |
itemsGroup.append('g').attr('class', 'hovered'); | |
selectedGroup.append('g').attr('class', 'hovered'); | |
} | |
//itemsGroup.selectAll('circle').remove(); | |
var selection = itemsGroup.selectAll('circle').data(items, function(d) { return d.id; }); | |
selection | |
.enter() | |
.insert('circle', ':first-child') | |
.attr('id', function (d) { | |
return sprintf('%s-item-%s-%s', self._id, d.seriesIndex, d.valueItems[0][0].globalIndex); | |
}) | |
.style('opacity', 0) | |
.style('fill-opacity', 0) | |
.attr('r', 0); | |
selection | |
.each( | |
/** | |
* @param {epiviz.ui.charts.UiObject} d | |
*/ | |
function(d) { | |
var circle = d3.select(this); | |
circle | |
.attr('cx', margins.left() + (d.values[0] - minX) * (width - margins.sumAxis(Axis.X)) / (maxX - minX)) | |
.attr('cy', height - margins.bottom() - ((d.values[1] - minY) * (height - margins.sumAxis(Axis.Y)) / (maxY - minY))) | |
.attr('class', d.cssClasses) | |
.style('fill', self.colors().get(d.seriesIndex + 1)); | |
}); | |
selection | |
.transition() | |
.duration(1000) | |
.style('fill-opacity', function(d) { | |
return Math.max(0.3, d.valueItems[0].length / maxGroupItems); | |
}) | |
.style('opacity', null) | |
.attr('r', circleRadius); | |
selection | |
.exit() | |
.transition() | |
.duration(1000) | |
.style('opacity', 0) | |
.attr('r', 0) | |
.remove(); | |
selection | |
.on('mouseover', function (d) { | |
self._hover.notify(d); | |
}) | |
.on('mouseout', function (d) { | |
self._unhover.notify(); | |
}) | |
.on('click', function (d) { | |
self._deselect.notify(); | |
self._select.notify(d); | |
d3.event.stopPropagation(); | |
}); | |
}; | |
/** | |
* @returns {string} | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype.chartTypeName = function() { return 'epiviz.plugins.charts.ScatterPlot'; }; | |
/** | |
* @returns {Array.<{name: string, color: string}>} | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype.colorMap = function() { | |
var self = this; | |
var n = Math.min(this._measurementsX.length, this._measurementsY.length); | |
var colors = new Array(n); | |
for (var i = 0; i < n; ++i) { | |
colors[i] = { | |
name: sprintf('%s vs %s', this._measurementsX[i].name(), this._measurementsY[i].name()), | |
color: this._properties.colors.get(i) | |
}; | |
} | |
return colors; | |
}; | |
/** | |
* @param xScale D3 linear scale for the x axis | |
* @param yScale D3 linear scale for the y axis | |
* @param {number} [xTicks] | |
* @param {number} [yTicks] | |
* @param [svg] D3 svg container for the axes | |
* @param {number} [width] | |
* @param {number} [height] | |
* @param {epiviz.ui.charts.Margins} [margins] | |
* @protected | |
*/ | |
epiviz.plugins.charts.ScatterPlot.prototype._drawAxes = function(xScale, yScale, xTicks, yTicks, svg, width, height, margins) { | |
epiviz.ui.charts.Plot.prototype._drawAxes.call(this, xScale, yScale, xTicks, yTicks, svg, width, height, margins); | |
this._legend.selectAll('text').remove(); | |
var xWidth = this._xLabel.length * 7; | |
var yWidth = this._yLabel.length * 7; | |
this._legend.append('text') | |
.attr('x', (this.width() - xWidth) * 0.5) | |
.attr('y', (this.height() - this.margins().bottom() + 35)) | |
.attr('style', 'font-weight: regular; font-size: 12;') | |
.attr('fill', '#000000') | |
.attr('stroke', 'none') | |
.style('fill-opacity', 1) | |
.text(this._xLabel); | |
this._legend.append('text') | |
.attr('x', - this.height() + (this.height() - yWidth) * 0.5 + this.margins().top()) | |
.attr('y', this.margins().left() - 25) | |
.attr('transform', 'rotate(-90)') | |
.attr('style', 'font-weight: regular; font-size: 12;') | |
.attr('fill', '#000000') | |
.attr('stroke', 'none') | |
.style('fill-opacity', 1) | |
.text(this._yLabel); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment