@domoinc/image-tooltip
Version:
ImageTooltip - Domo Widget
397 lines (352 loc) • 13.5 kB
JavaScript
/*----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
// ImageTooltip:
© 2011 - 2015 DOMO, INC.
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------*/
var d3 = require('d3');
var d3Chart = require('d3.chart');
var BaseWidget = require('@domoinc/base-widget');
var daTheme2 = require('@domoinc/da-theme2');
var navigate = require('./navigate');
var Tooltip = require('@domoinc/domo-tooltip');
module.exports = BaseWidget.extend('ImageTooltip', {
//**********************************************************************************
// Initialization
//**********************************************************************************
initialize: function () {
'use strict';
var _Chart = this;
//----------------------------------------------------------------------------------
// Config helper functions
//----------------------------------------------------------------------------------
function getMinWidthHeight() {
return d3.min([_Chart.c('width'), _Chart.c('height')]);
}
//----------------------------------------------------------------------------------
// Config
//----------------------------------------------------------------------------------
_Chart._newConfig = {
// Configurable configs
// Image configs
width: {
name: 'Image Width',
description: 'Width of the image',
value: 400,
type: 'number',
units: 'px'
},
height: {
name: 'Image Height',
description: 'Height of the image',
value: 400,
type: 'number',
units: 'px'
},
// Tooltip configs
tooltipWidth: {
name: 'Tooltip Width',
description: 'Width of the tooltip',
value: 200,
type: 'number',
units: 'px'
},
tooltipBackgroundColor: daTheme2.themeElements.tooltipBackground(),
font: daTheme2.themeElements.tooltipFontFamily(),
// Tooltip title configs
tooltipTitleFontWeight: daTheme2.themeElements.tooltipFontWeight({
name: 'Tooltip Title Font Weight',
}),
tooltipTitleFontSize: daTheme2.themeElements.tooltipFontSize({
name: 'Tooltip Title Font Size',
description: 'Font size for the tooltip title',
}),
tooltipTitleFontColor: daTheme2.themeElements.tooltipFontColor({
name: 'Tooltip Title Font Color',
description: 'Font color for the tooltip title',
}),
// Tooltip body configs
tooltipBodyFontWeight: daTheme2.themeElements.tooltipFontWeight({
name: 'Tooltip Body Font Weight',
}),
tooltipBodyFontSize: daTheme2.themeElements.tooltipFontSize({
name: 'Tooltip Body Font Size',
description: 'Font size for the tooltip body text',
}),
tooltipBodyFontColor: daTheme2.themeElements.tooltipFontColor({
name: 'Tooltip Body Font Color',
description: 'Font color for the tooltip body text',
}),
// Non configurable configs
chartName: {
description: 'Name of chart for Reporting.',
type: 'string',
value: 'ImageTooltip'
},
showErrorMessage: {
type: 'boolean',
value: true
}
};
_Chart.mergeConfig(_Chart._newConfig);
//----------------------------------------------------------------------------------
// Data Definition
//----------------------------------------------------------------------------------
_Chart._newDataDefinition = {
'Title': {
type: 'string',
validate: function(d) { return true; },
accessor: function(line) { return line[0]; }
},
'Text': {
type: 'string',
validate: function(d) { return true; },
accessor: function(line) { return line[1]; }
},
'ImageURL': {
type: 'string',
validate: function(d) { return true; },
accessor: function(line) { return line[2]; }
},
'LinkURL': {
type: 'string',
validate: function(d) { return true; },
accessor: function(line) { return line[3]; }
},
};
_Chart.mergeDataDefinition(_Chart._newDataDefinition);
//----------------------------------------------------------------------------------
// Local variables
//----------------------------------------------------------------------------------
var imageGroup = _Chart._layerGroup.append('g').classed('imageGroup', true);
_Chart._svg = findSvg(_Chart._layerGroup.node());
var tooltipSvg = d3.select('body').append('div')
.classed('tooltipDiv', true)
.style({
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
left: 0,
'z-index': 10000, // needs to be over 9000 to go over the dropdown menu
'pointer-events': 'none',
})
.append('svg')
.attr({
height: '100%',
width: '100%',
});
_Chart._tooltip = tooltipSvg
.chart('DomoTooltip')
.c('container', tooltipSvg)
.c('format', 'multiTextBlock');
_Chart._tooltip._appends.append('text').classed('title', true);
_Chart._tooltip._appends.append('text').classed('body', true);
//----------------------------------------------------------------------------------
// Triggers and event listeners
//----------------------------------------------------------------------------------
//**********************************************************************************
// d3.Chart Transform
//**********************************************************************************
var superTransform = _Chart.transform;
_Chart.transform = function (data) {
var validData = superTransform(data);
insertImage(data);
updateTooltipStyles();
return validData;
};
//----------------------------------------------------------------------------------
// Event functions
//----------------------------------------------------------------------------------
function hoverMove(d) {
var mouse = d3.mouse(_Chart._svg.node());
var point = { x: mouse[0], y: mouse[1] };
_Chart._tooltip._appends.select('.title').text(_Chart.a('Title')(d));
_Chart._tooltip._appends.select('.body').text(_Chart.a('Text')(d));
_Chart._tooltip.trigger('draw');
_Chart._tooltip.trigger('moveTo', point);
}
function hoverOff() {
_Chart._tooltip.trigger('remove');
}
//----------------------------------------------------------------------------------
// Tooltip functions
//----------------------------------------------------------------------------------
function updateTooltipStyles() {
_Chart._tooltip
.c('maxWidth', _Chart.c('tooltipWidth'))
.c('tooltipBackgroundColor', _Chart.c('tooltipBackgroundColor'));
_Chart._tooltip._appends
.select('.title')
.style({
'font-family': _Chart.c('font'),
'font-weight': _Chart.c('tooltipTitleFontWeight').value,
'font-size': _Chart.c('tooltipTitleFontSize'),
'fill': _Chart.c('tooltipTitleFontColor'),
});
_Chart._tooltip._appends
.select('.body')
.style({
'font-family': _Chart.c('font'),
'font-weight': _Chart.c('tooltipBodyFontWeight').value,
'font-size': _Chart.c('tooltipBodyFontSize'),
'fill': _Chart.c('tooltipBodyFontColor'),
});
}
//----------------------------------------------------------------------------------
// Helper functions
//----------------------------------------------------------------------------------
function findSvg(selection) {
var selectionParent = selection.parentNode;
if (selectionParent.tagName === 'svg') {
return d3.select(selectionParent);
} else {
return findSvg(selectionParent);
}
}
//----------------------------------------------------------------------------------
// Image functions
//----------------------------------------------------------------------------------
/**
* load image if there is a valid url
* otherwise give an error image
*/
function insertImage(data) {
imageGroup.selectAll('*').remove();
var container = addAnchor(imageGroup, _Chart.a('LinkURL')(data[0]));
var img = new Image();
img.src = _Chart.a('ImageURL')(data[0]);
img.onload = function() {
updateImageAttrs(container, this, data[0]);
};
img.onerror = function() {
if (_Chart.c('showErrorMessage')) {
addImageFailedMessage(container);
}
};
}
function updateImageAttrs(container, imgObj, data) {
var svgImage = container.append('image')
.datum(data)
.attr({
'image-rendering': 'optimizeQuality',
'x': 0,
'y': 0
})
.style({
opacity: 1,
display: 'inline',
})
.on('mousemove', hoverMove)
.on('mouseout', hoverOff);
var chartRatio = _Chart.c('width') / _Chart.c('height');
var width;
var height;
var transform;
var imgWidth = imgObj.width;
var imgHeight = imgObj.height;
var imageRatio = imgWidth / imgHeight;
if (imageRatio > chartRatio) {
width = _Chart.c('width');
height = _Chart.c('width') * imgHeight / imgWidth;
transform = 'translate(0, ' + ((_Chart.c('height') - height) / 2) + ')';
} else {
width = _Chart.c('height') * imgWidth / imgHeight;
height = _Chart.c('height');
transform = 'translate(' + ((_Chart.c('width') - width) / 2) + ', 0)';
}
svgImage.attr({
'xlink:href': imgObj.src,
'width': width,
'height': height,
'transform': transform
});
}
function addImageFailedMessage(container) {
var chartRatio = _Chart.c('width') / _Chart.c('height');
var imageWidth = 143.804;
var imageHeight = 185.958;
var imageRatio = imageWidth / imageHeight;
var scale;
var imageShapePath = 'M131.82,12.096H11.984v96.767H131.82V12.096L131.82,12.096z M143.804,119.761 c0,0.661-0.535,1.197-1.2,1.197H1.2c-0.663,0-1.2-0.539-1.2-1.197V1.197C0,0.536,0.535,0,1.2,0h141.403c0.663,0,1.2,0.539,1.2,1.197 L143.804,119.761L143.804,119.761z M119.836,96.767H23.968V77.759l28.76-47.52l38.348,47.519l28.761-19.008L119.836,96.767 L119.836,96.767z M85.437,38.783c0-5.213,2.755-10.03,7.228-12.637c4.473-2.607,9.983-2.607,14.456,0 c4.473,2.607,7.228,7.424,7.228,12.637c0,5.213-2.755,10.03-7.228,12.637c-4.473,2.607-9.983,2.607-14.456,0 S85.437,43.996,85.437,38.783L85.437,38.783z';
var exclamationShape = 'M0,22.82h26.365L13.183,0L0,22.82z M13.183,4.795l9.025,15.623H4.158L13.183,4.795z M14.381,19.217h-2.397v-2.402h2.397V19.217z M11.984,15.614V9.609h2.397v6.005H11.984z';
var fontSize = 10;
var transformX;
var transformY;
if (imageRatio > chartRatio) {
scale = (_Chart.c('width') * 0.8) / imageWidth;
} else {
scale = (_Chart.c('height') * 0.8) / imageHeight;
}
transformX = (_Chart.c('width') - imageWidth * scale) / 2;
transformY = (_Chart.c('height') - imageHeight * scale) / 2;
var imageFailedGroupScale = container.append('g')
.attr('transform', 'translate(' + transformX + ', ' + transformY + ')');
var imageFailedGroup = imageFailedGroupScale.append('g')
.attr('transform', 'scale(' + scale + ')');
imageFailedGroup.append('path')
.attr({
'd': exclamationShape,
'transform': 'translate(' + 59 + ', ' + 0 + ')'
})
.style({
'fill': '#fb9995'
});
var textGroup = imageFailedGroup.append('g')
.attr({
'transform': 'translate(' + 72 + ', ' + 40 + ')'
});
textGroup.append('text')
.text('Your image has failed to load.')
.style({
'font-size': fontSize + 'px',
'text-anchor': 'middle',
'fill': '#808080'
});
textGroup.append('text')
.text('Try checking your image URL.')
.attr({
'y': fontSize * 1.35,
})
.style({
'font-size': fontSize + 'px',
'text-anchor': 'middle',
'fill': '#999999'
});
imageFailedGroup.append('path')
.attr({
'd': imageShapePath,
'transform': 'translate(' + 0 + ', ' + 65 + ')'
})
.style({
'fill': '#e6e6e6'
});
// an invisible rect which captures mouse events
imageFailedGroup.append('rect')
.attr({
width: imageWidth,
height: imageHeight
})
.style({
opacity: 0
});
}
function clickLink(url) {
return function() {
d3.event.preventDefault();
navigate(url, true);
};
}
function addAnchor(selection, url) {
if (url) {
return selection.append('a')
.attr({
'xlink:href': url,
'target': '_blank'
})
.on('click', clickLink(url));
}
return selection;
}
}
});