UNPKG

@domoinc/image-tooltip

Version:

ImageTooltip - Domo Widget

397 lines (352 loc) 13.5 kB
/*---------------------------------------------------------------------------------- ---------------------------------------------------------------------------------- // 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; } } });