import get from 'lodash/get'; import { wordwrap } from 'd3-jetpack'; import { select } from 'd3-selection'; import numeral from 'numeral'; import { isListedToText } from 'di2-types'; import { SG_CLASS, HOVER_MENU_WIDTH, ConnectionCompanySVGSelection, SG_CLASS_HOVER_MENU } from './subsidiary-list-group.class'; import { translateString } from '../util/svg'; import * as d3Shape from 'd3-shape'; import { IConnectionCompanyData } from '../connection-map.component'; import { limitedSectorText, selectionHeight } from './utilities'; import { COLOUR } from '../util/constants'; export const CLASS_ACTION_COMPANY_PAGE = `${SG_CLASS_HOVER_MENU}--action-company-page`; export const CLASS_ACTION_TARGET_LIST = `${SG_CLASS_HOVER_MENU}--action-target-list`; const ROW_SPACING = 10; const LEFT_PADDING = 10; interface ICoord { x: number; y: number; } const textLarge = { fontSize: 16, lineHeight: 24, fontFamily: 'MetricWeb, sans-serif', fontWeight: 400 }; const textMedium = { fontSize: 14, fontWeight: 400 }; const textSmall = { fontSize: 12, fontWeight: 400 }; const textLargeBlack = { ...textLarge, fill: COLOUR.SCOUT_BLACK }; const textMediumScorpion = { ...textMedium, fill: COLOUR.SCORPION }; const textMediumDarkTeal = { ...textMedium, fill: COLOUR.DARK_TEAL }; const textSmallAluminium = { ...textSmall, fill: COLOUR.ALUMINIUM }; const underline = (selection, color: string, { x, y }: ICoord) => selection.append('line').at({ stroke: color, strokeWidth: 1, x1: x, x2: x + HOVER_MENU_WIDTH - LEFT_PADDING * 2, y1: y, y2: y }); const nonInlineRow = ( selection, x: number, y: number, heading: string, text: string | number ): ConnectionCompanySVGSelection => { text = String(text); const rowGroup = selection.append('g').at({ x, y }); const textGroup = rowGroup.append('g').at({ x, y }); const label = textGroup .append('text') .at({ ...textSmallAluminium, y, x: x + LEFT_PADDING }) .text(heading); textGroup .append('text') .at({ ...textMediumScorpion, y: y + selectionHeight(label) + 5 }) .tspans(wordwrap(text, 32)) .attr('x', x + LEFT_PADDING); underline(rowGroup, COLOUR.ALUMINIUM, { x: x + LEFT_PADDING, y: y + selectionHeight(textGroup) }); return rowGroup; }; const row = ( selection, x: number, y: number, labelText: string, valueText: string | number ): ConnectionCompanySVGSelection => { valueText = String(valueText); const rowGroup = selection.append('g').at({ x, y }); const textGroup = rowGroup.append('g').at({ x, y }); textGroup .append('text') .at({ ...textSmallAluminium, x: x + LEFT_PADDING, y }) .text(labelText); textGroup .append('text') .at({ ...textMediumScorpion, y }) .tspans(wordwrap(valueText, 15)) .at({ textAnchor: 'end', x: x + HOVER_MENU_WIDTH - LEFT_PADDING }); underline(rowGroup, COLOUR.ALUMINIUM, { x: x + LEFT_PADDING, y: y + selectionHeight(textGroup) - 5 }); return rowGroup; }; const hoverMenu = (selection, { x, y }: ICoord) => selection .append('g') .append('rect') .at({ width: HOVER_MENU_WIDTH, fill: 'white', stroke: COLOUR.ALUMINIUM, x, y }); const headingRow = (selection, headingText: string, { x, y }: ICoord) => { const rowGroup = selection.append('g').at({ x, y }); const textGroup = rowGroup.append('g').at({ x, y }); textGroup .append('text') .at({ ...textLargeBlack, y }) .tspans(wordwrap(headingText, 25)) .attr('x', x + LEFT_PADDING); underline(rowGroup, 'black', { x: x + LEFT_PADDING, y: y + selectionHeight(textGroup) - 5 }); return rowGroup; }; const linkRow = ( selection, linkText: string, clickHandler, { x, y }: ICoord, className: string ) => selection .append('text') .at({ ...textMediumDarkTeal, x: x + LEFT_PADDING, y, class: className }) .text(linkText) .on('click', () => clickHandler()) // execute as standalone function to prevent passing d3 arguments to event handler .on('mouseover', (d, i, nodes) => select(nodes[i]).attr('cursor', 'pointer') ); const toolTipPointer = (selection, { x, y }: ICoord) => selection .append('path') .attr('transform', () => translateString(HOVER_MENU_WIDTH / 2 + x, -2 + y) ) .attr('d', d3Shape.symbol().type(d3Shape.symbolTriangle)) .attr('class', `${SG_CLASS}__hover-menu-pointer`); const ownershipText = (data: IConnectionCompanyData): string => isListedToText(get(data, ['company', 'isListed'], '')); const revenueText = (data: IConnectionCompanyData): string => numeral(get(data, ['company', 'operatingRevenue'], 'Unknown')).format( '$0.000a' ); const buildScoutHoverMenu = ( selection, { x, y }: ICoord, data: IConnectionCompanyData, { addToTargetList, companyPageNavigate } ) => { const menu = hoverMenu(selection, { x, y }); let menuHeight = 25; const nameRow = headingRow(selection, data.name, { x, y: y + menuHeight }); menuHeight += selectionHeight(nameRow) + ROW_SPACING; const countryRow = row( selection, x, y + menuHeight, 'Country or region', data.country ); menuHeight += selectionHeight(countryRow) + ROW_SPACING; const ownershipRow = row( selection, x, y + menuHeight, 'Ownership', ownershipText(data) ); menuHeight += selectionHeight(ownershipRow) + ROW_SPACING; const revenueRow = row( selection, x, y + menuHeight, 'Revenue', revenueText(data) ); menuHeight += selectionHeight(revenueRow) + ROW_SPACING; const sectorRow = nonInlineRow( selection, x, y + menuHeight, 'Sector', limitedSectorText(100, data) ); menuHeight += selectionHeight(sectorRow) + ROW_SPACING; const subsidiariesRow = row( selection, x, y + menuHeight, 'Total Subsidiaries', data.subsidiaryCount ); menuHeight += selectionHeight(subsidiariesRow) + ROW_SPACING; const targetListLinkRow = linkRow( selection, 'Add to Target List', addToTargetList.bind(null, data.company.scoutId), { x, y: y + menuHeight }, CLASS_ACTION_TARGET_LIST ); menuHeight += selectionHeight(targetListLinkRow) + ROW_SPACING; const companyPageLinkRow = linkRow( selection, 'Go to Company Page', companyPageNavigate.bind(null, data.company.scoutId), { x, y: y + menuHeight }, CLASS_ACTION_COMPANY_PAGE ); menuHeight += selectionHeight(companyPageLinkRow); menu.attr('height', menuHeight); toolTipPointer(selection, { x, y }); }; const buildNonScoutHoverMenu = ( selection, { x, y }, data: IConnectionCompanyData ) => { const menu = hoverMenu(selection, { x, y }); let height = y + 25; const name = headingRow(selection, data.name, { x, y: height }); height += selectionHeight(name) + ROW_SPACING; const country = row( selection, x, height, 'Country or region', data.country ); height += selectionHeight(country) + ROW_SPACING; const subsidiaries = row( selection, x, height, 'Subsidiaries', data.subsidiaryCount ); height += selectionHeight(subsidiaries); menu.attr('height', height - y); toolTipPointer(selection, { x, y }); }; export const buildHoverMenu = ( selection, coords: ICoord, data: IConnectionCompanyData, actions ) => data.isScoutCompany ? buildScoutHoverMenu(selection, coords, data, actions) : buildNonScoutHoverMenu(selection, coords, data);