morpheus.VectorTrackHeader = function(project, name, isColumns, controller) { morpheus.AbstractCanvas.call(this); this.project = project; this.name = name; this.isColumns = isColumns; var canvas = this.canvas; this.controller = controller; var vector = (isColumns ? project.getFullDataset().getColumnMetadata() : project.getFullDataset().getRowMetadata()).getByName(name); if (vector && vector.getProperties().has(morpheus.VectorKeys.TITLE)) { this.canvas.setAttribute('title', vector.getProperties().get( morpheus.VectorKeys.TITLE)); $(this.canvas).tooltip(); } var _this = this; this.setBounds({ height : this.defaultFontHeight + morpheus.VectorTrackHeader.FONT_OFFSET }); function getResizeCursor(pos) { if (isColumns) { if (pos.y >= (_this.getUnscaledHeight() - 3)) { return 'ns-resize'; } } if (pos.x >= (_this.getUnscaledWidth() - 3)) { return 'ew-resize'; } } var mouseMove = function(event) { if (!morpheus.CanvasUtil.dragging) { var pos = morpheus.CanvasUtil.getMousePos(event.target, event); var cursor = getResizeCursor(pos); canvas.style.cursor = !cursor ? 'default' : cursor; _this.isMouseOver = true; _this.repaint(); } }; var mouseExit = function(e) { if (!morpheus.CanvasUtil.dragging) { canvas.style.cursor = 'default'; } _this.isMouseOver = false; _this.repaint(); }; var showPopup = function(e) { controller.setSelectedTrack(_this.name, isColumns); var track = controller.getTrack(_this.name, isColumns); if (!track) { throw _this.name + ' track not found'; } e.preventDefault(); if (e.stopPropagation) { e.stopPropagation(); } if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); } track.showPopup(e, true); return false; }; this.selectedBackgroundColor = '#c8c8c8'; this.backgroundColor = '#f9f9f9'; $(this.canvas).css('background-color', this.backgroundColor).on( 'mousemove.morpheus', mouseMove).on('mouseout.morpheus', mouseExit) .on('mouseenter.morpheus', mouseMove); $(this.canvas).on('contextmenu.morpheus', showPopup); var resizeCursor; var dragStartWidth = 0; var dragStartHeight = 0; var dragging = false; var dragStartPosition; // var throttled = _.throttle(function(event) { // // if (event.type === 'mouseout') { // } else { // } // }, 100); // $(canvas).on('mouseout', throttled).on('mousemove', throttled); this.hammer = morpheus.Util .hammer(canvas, [ 'pan', 'tap', 'longpress' ]) .on('longpress', function(event) { event.preventDefault(); controller.setSelectedTrack(_this.name, isColumns); var track = controller.getTrack(_this.name, isColumns); track.showPopup(event.srcEvent, true); }) .on( 'panend', function(event) { _this.isMouseOver = false; morpheus.CanvasUtil.dragging = false; canvas.style.cursor = 'default'; var index = controller.getTrackIndex(_this.name, isColumns); var header = controller.getTrackHeaderByIndex(index, isColumns); var track = controller .getTrackByIndex(index, isColumns); var $canvas = $(track.canvas); $canvas.css('z-index', '0'); $(header.canvas).css('z-index', '0'); controller.revalidate(); }) .on( 'panstart', function(event) { _this.isMouseOver = false; if (morpheus.CanvasUtil.dragging) { return; } resizeCursor = getResizeCursor(morpheus.CanvasUtil .getMousePos(event.target, event, true)); if (resizeCursor) { // make sure start event was on // hotspot morpheus.CanvasUtil.dragging = true; canvas.style.cursor = resizeCursor; dragStartWidth = _this.getUnscaledWidth(); dragStartHeight = _this.getUnscaledHeight(); event.preventDefault(); dragging = false; } else { var index = controller.getTrackIndex(_this.name, isColumns); if (index == -1) { throw _this.name + ' not found'; } var header = controller.getTrackHeaderByIndex( index, isColumns); var track = controller.getTrackByIndex(index, isColumns); controller.setSelectedTrack(_this.name, isColumns); var $canvas = $(track.canvas); dragStartPosition = $canvas.position(); $canvas.css('z-index', '100'); $(header.canvas).css('z-index', '100'); morpheus.CanvasUtil.dragging = true; resizeCursor = undefined; dragging = true; } }) .on( 'panmove', function(event) { _this.isMouseOver = false; if (resizeCursor) { var width; var height; if (resizeCursor === 'ew-resize') { var dx = event.deltaX; width = Math.max(8, dragStartWidth + dx); } if (resizeCursor === 'ns-resize') { var dy = event.deltaY; height = Math.max(8, dragStartHeight + dy); } controller.resizeTrack(_this.name, width, height, isColumns); } else if (dragging) { // reorder var index = controller.getTrackIndex(_this.name, isColumns); var header = controller.getTrackHeaderByIndex( index, isColumns); var track = controller.getTrackByIndex(index, isColumns); var ntracks = controller.getNumTracks(isColumns); var delta = isColumns ? event.deltaY : event.deltaX; var newIndex = index + (delta > 0 ? 1 : -1); newIndex = Math.min(Math.max(0, newIndex), ntracks - 1); var prop = isColumns ? 'top' : 'left'; var w = isColumns ? 'getUnscaledHeight' : 'getUnscaledWidth'; var trackBounds = {}; trackBounds[prop] = dragStartPosition[prop] + delta; track.setBounds(trackBounds); header.setBounds(trackBounds); var dragOverTrack = controller.getTrackByIndex( newIndex, isColumns); var dragOverWidth = dragOverTrack[w](); var dragOverLeft = $(dragOverTrack.canvas) .position()[prop]; var dragleft = dragStartPosition[prop] + delta; var dragright = dragleft + track[w](); if ((delta > 0 && dragright >= dragOverLeft + dragOverWidth / 2) || (delta < 0 && dragleft <= dragOverLeft + dragOverWidth / 2)) { if (index !== newIndex) { controller.moveTrack(index, newIndex, isColumns); var otherHeader = controller .getTrackHeaderByIndex(index, isColumns); var otherTrack = controller .getTrackByIndex(index, isColumns); var $movedCanvas = $(otherTrack.canvas); var newLeft = $movedCanvas.position()[prop]; if (delta < 0) { newLeft += track[w](); } else { newLeft -= track[w](); } var otherBounds = {}; otherBounds[prop] = newLeft; otherTrack.setBounds(otherBounds); otherHeader.setBounds(otherBounds); } } } }) .on( 'tap', function(event) { if (morpheus.Util.IS_MAC && event.srcEvent.ctrlKey) { // right-click return; } _this.isMouseOver = false; controller.setSelectedTrack(_this.name, isColumns); if (isColumns && !controller.options.columnsSortable) { return; } if (!isColumns && !controller.options.rowsSortable) { return; } var additionalSort = event.srcEvent.shiftKey; var isGroupBy = false; // event.srcEvent.altKey; var existingSortKeyIndex = _this .getSortKeyIndexForColumnName(_this .getSortKeys(), _this.name); var sortOrder; var sortKey; if (existingSortKeyIndex != -1) { sortKey = _this.getSortKeys()[existingSortKeyIndex]; if (sortKey.getSortOrder() === morpheus.SortKey.SortOrder.UNSORTED) { sortOrder = morpheus.SortKey.SortOrder.ASCENDING; // 1st // click } else if (sortKey.getSortOrder() === morpheus.SortKey.SortOrder.ASCENDING) { sortOrder = morpheus.SortKey.SortOrder.DESCENDING; // 2nd // click } else { sortOrder = morpheus.SortKey.SortOrder.UNSORTED; // 3rd // click } } else { sortKey = new morpheus.SortKey(_this.name, morpheus.SortKey.SortOrder.ASCENDING); sortOrder = morpheus.SortKey.SortOrder.ASCENDING; } if (sortKey != null) { sortKey.setSortOrder(sortOrder); _this.setSortingStatus(_this.getSortKeys(), sortKey, additionalSort, isGroupBy); } // } }); }; morpheus.VectorTrackHeader.FONT_OFFSET = 2; morpheus.VectorTrackHeader.prototype = { selected : false, isMouseOver : false, defaultFontHeight : 11, dispose : function() { morpheus.AbstractCanvas.prototype.dispose.call(this); $(this.canvas) .off( 'contextmenu.morpheus mousemove.morpheus mouseout.morpheus mouseenter.morpheus'); this.hammer.destroy(); }, getPreferredSize : function() { var size = this.getPrintSize(); size.width += 22; if (!this.isColumns) { size.height = this.defaultFontHeight + morpheus.VectorTrackHeader.FONT_OFFSET; } // var vector = (this.isColumns ? this.project.getFullDataset() // .getColumnMetadata() : this.project.getFullDataset() // .getRowMetadata()).getByName(this.name); // if (vector // && vector.getProperties().get( // morpheus.VectorKeys.SHOW_HEADER_SUMMARY)) { // if (isNaN(size.height)) { // size.height = 0; // } // if (!this.isColumns) { // size.height += 50; // } else { // size.width += 50; // } // // } return size; }, getPrintSize : function() { var context = this.canvas.getContext('2d'); context.font = this.defaultFontHeight + 'px ' + morpheus.CanvasUtil.FONT_NAME; var width = 4 + context.measureText(this.name).width; return { width : width, height : this.getUnscaledHeight() }; }, getSortKeys : function() { return this.isColumns ? this.project.getColumnSortKeys() : this.project .getRowSortKeys(); }, setOrder : function(sortKeys) { if (this.isColumns) { this.project.setColumnSortKeys(morpheus.SortKey .keepExistingSortKeys(sortKeys, this.project .getColumnSortKeys()), false); } else { this.project.setRowSortKeys(morpheus.SortKey.keepExistingSortKeys( sortKeys, this.project.getRowSortKeys()), false); } }, setGroupBy : function(groupBy) { var existingGroupBy = this.isColumns ? this.project.groupColumns : this.project.groupRows; // see if already exists, if so remove it var index = -1; for (var i = 0, length = existingGroupBy.length; i < length; i++) { if (existingGroupBy[i].toString() === groupBy.toString()) { index = i; break; } } var newGroupBy = [ groupBy ]; if (index !== -1) { newGroupBy = existingGroupBy; newGroupBy.splice(index, 1); } if (this.isColumns) { this.project.setGroupColumns(newGroupBy, true); } else { this.project.setGroupRows(newGroupBy, true); } }, setSelected : function(selected) { if (selected != this.selected) { this.selected = selected; $(this.canvas) .css( { 'background-color' : this.selected ? this.selectedBackgroundColor : this.backgroundColor }); } }, setSortingStatus : function(sortKeys, sortKey, additionalSort, isGroupBy) { if (!isGroupBy) { if (sortKey.getSortOrder() == morpheus.SortKey.SortOrder.UNSORTED && !additionalSort) { this.setOrder([]); } else { if (additionalSort && sortKeys.length == 0) { additionalSort = false; } if (!additionalSort) { sortKeys = [ sortKey ]; } else { var sortKeyIndex = this.getSortKeyIndexForColumnName( sortKeys, sortKey.toString()); if (sortKeyIndex === -1) { // new sort column sortKeys.push(sortKey); } else { // change sort order of existing sort column sortKeys[sortKeyIndex] = sortKey; } } this.setOrder(sortKeys); } } if (isGroupBy) { this.setGroupBy(sortKey); } else { if (this.isColumns) { this.project.trigger('columnSortOrderChanged'); } else { this.project.trigger('rowSortOrderChanged'); } } }, getSortKeyIndexForColumnName : function(sortKeys, columnName) { if (sortKeys != null) { for (var i = 0, size = sortKeys.length; i < size; i++) { if (columnName === sortKeys[i].toString()) { return i; } } } return -1; }, print : function(clip, context) { if (clip.height <= 6) { return; } context.textBaseline = 'bottom'; if (this.isColumns) { context.textAlign = 'right'; context.font = Math.min(this.defaultFontHeight, clip.height - morpheus.VectorTrackHeader.FONT_OFFSET) + 'px ' + morpheus.CanvasUtil.FONT_NAME; } else { context.textAlign = 'left'; context.font = (clip.height - morpheus.VectorTrackHeader.FONT_OFFSET) + 'px ' + morpheus.CanvasUtil.FONT_NAME; } context.fillStyle = morpheus.CanvasUtil.FONT_COLOR; context.fillText(this.name, 0, 0); }, draw : function(clip, context) { var sortKeys = this.getSortKeys(); var name = this.name; var existingSortKeyIndex = this.getSortKeyIndexForColumnName(sortKeys, name); morpheus.CanvasUtil.resetTransform(context); context.clearRect(0, 0, this.getUnscaledWidth(), this .getUnscaledHeight()); if (this.getUnscaledHeight() < 5) { return; } if (this.isColumns) { context.beginPath(); context.moveTo(0, this.getUnscaledHeight()); context.lineTo(this.getUnscaledWidth(), this.getUnscaledHeight()); context.stroke(); context.textAlign = 'right'; } else { context.beginPath(); context.moveTo(this.getUnscaledWidth(), 0); context.lineTo(this.getUnscaledWidth(), this.getUnscaledHeight()); context.stroke(); context.textAlign = 'left'; } var fontHeight = Math.min(this.defaultFontHeight, this .getUnscaledHeight() - morpheus.VectorTrackHeader.FONT_OFFSET); var squished = this.controller.getTrack(this.name, this.isColumns).settings.squished; context.font = (squished ? 'Italic ' : '') + fontHeight + 'px ' + morpheus.CanvasUtil.FONT_NAME; var textWidth = context.measureText(name).width; var isColumns = this.isColumns; var xpix = this.isColumns ? this.getUnscaledWidth() - 2 : 10; if (isColumns) { if (existingSortKeyIndex != -1) { xpix -= 6; } if (sortKeys.length > 1) { xpix -= 6; } } var ypix = this.isColumns ? (this.getUnscaledHeight() / 2) : (this.getUnscaledHeight() - (this.defaultFontHeight + morpheus.VectorTrackHeader.FONT_OFFSET) / 2); context.textBaseline = 'middle'; if (this.isMouseOver) { context.fillStyle = 'rgb(0,0,0)'; var xdot = xpix - (isColumns ? textWidth + 4 : 4); var ydot = ypix - 3; for (var i = 0; i < 2; i++) { for (var j = 0; j < 3; j++) { context.fillRect(xdot - i * 3, ydot + j * 3, 1.5, 1.5); } } } context.fillStyle = morpheus.CanvasUtil.FONT_COLOR; context.fillText(name, xpix, ypix); // var vector = (this.isColumns ? this.project.getFullDataset() // .getColumnMetadata() : this.project.getFullDataset() // .getRowMetadata()).getByName(this.name); // if (vector // && vector.getProperties().get( // morpheus.VectorKeys.SHOW_HEADER_SUMMARY)) { // var summary = vector.getProperties().get( // morpheus.VectorKeys.HEADER_SUMMARY); // var track = this.controller.getTrack(this.name, this.isColumns); // if (summary == null) { // var visibleFieldIndices = vector.getProperties().get( // morpheus.VectorKeys.VISIBLE_FIELDS); // // if (visibleFieldIndices == null) { // visibleFieldIndices = morpheus.Util // .seq(vector.getProperties().get( // morpheus.VectorKeys.FIELDS).length); // } // var bigArray = []; // var min = Number.MAX_VALUE; // var max = Number.MAX_VALUE; // for (var i = 0, size = vector.size(); i < size; i++) { // var array = vector.getValue(i); // if (array != null) { // for (var j = 0, length = visibleFieldIndices.length; j < length; j++) // { // var value = array[visibleFieldIndices[j]]; // if (!isNaN(value)) { // bigArray.push(value); // min = value < min ? value : min; // max = value > max ? value : max; // } // } // } // } // var nbins = Math.ceil(morpheus.Log2(bigArray.length) + 1); // var binSize = (max - min) / nbins; // var binNumberToOccurences = new Uint32Array(nbins); // var maxOccurences = 0; // for (var i = 0, size = bigArray.length; i < size; i++) { // var value = bigArray[i]; // var bin = Math.floor((value - min) / binSize); // if (bin < 0) { // bin = 0; // } else if (bin >= binNumberToOccurences.length) { // bin = binNumberToOccurences.length - 1; // } // binNumberToOccurences[bin]++; // maxOccurences = Math.max(maxOccurences, // binNumberToOccurences[bin]); // } // summary = { // box : morpheus.BoxPlotItem(morpheus.VectorUtil // .arrayAsVector(bigArray)), // histogram : { // binSize : binSize, // total : bigArray.length, // binNumberToOccurences : binNumberToOccurences, // maxOccurences : maxOccurences, // min : min, // max : max // } // // }; // // vector.getProperties().set(morpheus.VectorKeys.HEADER_SUMMARY, // summary); // } // var box = summary.box; // context.save(); // context.translate(1, 0); // // var scale = track.createChartScale(this.getUnscaledWidth() - 2); // // TODO // // make // // sure // // scale // // is // // the // // same // // as // // track // var itemSize = 12; // var pix = 1; // var start = pix + 1; // var end = pix + itemSize - 1; // var center = (start + end) / 2; // var _itemSize = itemSize - 2; // var lineHeight = Math.max(2, _itemSize - 8); // context.fillStyle = 'black'; // // box from q1 (25th q) to q3 // context.fillRect(Math.min(scale(box.q1), scale(box.q3)), start, // Math.abs(scale(box.q1) - scale(box.q3)), _itemSize); // // draw line from q1 to lav // context.fillRect(Math.min(scale(box.q1), // scale(box.lowerAdjacentValue)), center - lineHeight / 2, // Math.abs(scale(box.q1) - scale(box.lowerAdjacentValue)), // lineHeight); // // draw line from q3 to uav // context.fillRect(Math.min(scale(box.q3), // scale(box.upperAdjacentValue)), center - lineHeight / 2, // Math.abs(scale(box.q3) - scale(box.upperAdjacentValue)), // lineHeight); // var histogram = summary.histogram; // var yscale = d3.scale.linear().domain([ 0, 1 ]).range([ 48, 14 ]) // .clamp(true); // var xscale = d3.scale.linear().domain( // [ histogram.min, histogram.max + histogram.binSize ]) // .range([ 1, this.getUnscaledWidth() - 2 ]).clamp(true); // // context.beginPath(); // // context.moveTo(xscale(0), yscale(0)); // // for (var i = 0, nbins = histogram.binNumberToOccurences.length; i < // nbins; i++) { // var n = histogram.binNumberToOccurences[i]; // if (n > 0) { // var x = histogram.min + (i * histogram.binSize); // var xend = histogram.min + (i * histogram.binSize) // + histogram.binSize; // var xstart = histogram.min + (i * histogram.binSize); // var ypix = yscale(n / histogram.total); // context.fillRect(xscale(xstart), ypix, xscale(xend) // - xscale(xstart), Math.abs(ypix - yscale(0))); // } // } // // context.restore(); // } context.fillStyle = morpheus.CanvasUtil.FONT_COLOR; if (existingSortKeyIndex !== -1) { context.beginPath(); var x = this.isColumns ? xpix + 4 : xpix + textWidth + 6; var arrowHeight = Math.min(8, this.getUnscaledHeight() / 2 - 1); var arrowWidth = 3; if (sortKeys[existingSortKeyIndex].getSortOrder() === morpheus.SortKey.SortOrder.ASCENDING) { // up arrow context.translate(x, ypix - arrowHeight); context.moveTo(0, 0); context.lineTo(arrowWidth, arrowHeight); context.lineTo(-arrowWidth, arrowHeight); } else if (sortKeys[existingSortKeyIndex].getSortOrder() === morpheus.SortKey.SortOrder.DESCENDING) { // down // arrow context.translate(x, ypix); context.moveTo(0, arrowHeight); context.lineTo(arrowWidth, 0); context.lineTo(-arrowWidth, 0); } else { // diamond context.translate(x, ypix - arrowHeight / 2); context.moveTo(0, 0); context.lineTo(arrowWidth, arrowHeight / 2); context.lineTo(0, arrowHeight); context.lineTo(-arrowWidth, arrowHeight / 2); } context.fill(); morpheus.CanvasUtil.resetTransform(context); if (sortKeys.length > 1) { context.textAlign = 'left'; context.font = '8px ' + morpheus.CanvasUtil.FONT_NAME; context.fillText('' + (existingSortKeyIndex + 1), x + 4, ypix - 3); } } } }; morpheus.Util.extend(morpheus.VectorTrackHeader, morpheus.AbstractCanvas);