parent
f8c6fdbed7
commit
fe04ef3476
|
@ -4,6 +4,7 @@ import RightClickLayer from 'src/clockface/components/right_click_menu/RightClic
|
|||
import Nav from 'src/pageLayout'
|
||||
import Notifications from 'src/shared/components/notifications/Notifications'
|
||||
import LegendPortal from 'src/shared/components/LegendPortal'
|
||||
import TooltipPortal from 'src/shared/components/TooltipPortal'
|
||||
|
||||
interface Props {
|
||||
children: ReactChildren
|
||||
|
@ -15,6 +16,7 @@ const App: SFC<Props> = ({children}) => (
|
|||
<RightClickLayer />
|
||||
<Nav />
|
||||
<LegendPortal />
|
||||
<TooltipPortal />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -98,8 +98,6 @@ export const Histogram: SFC<Props> = ({
|
|||
/>
|
||||
{hoveredRowIndices && (
|
||||
<HistogramTooltip
|
||||
hoverX={hoverX}
|
||||
hoverY={hoverY}
|
||||
hoveredRowIndices={hoveredRowIndices}
|
||||
layer={layer}
|
||||
tooltip={tooltip}
|
||||
|
|
|
@ -1,82 +1,24 @@
|
|||
import React, {useRef, SFC} from 'react'
|
||||
import {SFC} from 'react'
|
||||
|
||||
import {HistogramTooltipProps, HistogramLayer} from 'src/minard'
|
||||
import {useLayoutStyle} from 'src/minard/utils/useLayoutStyle'
|
||||
import {useMousePos} from 'src/minard/utils/useMousePos'
|
||||
import {getHistogramTooltipProps} from 'src/minard/utils/getHistogramTooltipProps'
|
||||
|
||||
const MARGIN_X = 15
|
||||
const MARGIN_Y = 10
|
||||
|
||||
interface Props {
|
||||
hoverX: number
|
||||
hoverY: number
|
||||
tooltip?: (props: HistogramTooltipProps) => JSX.Element
|
||||
layer: HistogramLayer
|
||||
hoveredRowIndices: number[] | null
|
||||
}
|
||||
|
||||
const HistogramTooltip: SFC<Props> = ({
|
||||
hoverX,
|
||||
hoverY,
|
||||
tooltip,
|
||||
layer,
|
||||
hoveredRowIndices,
|
||||
}: Props) => {
|
||||
const tooltipEl = useRef<HTMLDivElement>(null)
|
||||
const {x: absoluteHoverX, y: absoluteHoverY} = useMousePos(document.body)
|
||||
|
||||
// Position the tooltip next to the mouse cursor, like this:
|
||||
//
|
||||
// ┌─────────────┐
|
||||
// │ │
|
||||
// (mouse) │ tooltip │
|
||||
// │ │
|
||||
// └─────────────┘
|
||||
//
|
||||
// The positioning is subject to the following restrictions:
|
||||
//
|
||||
// - If the tooltip overflows the right side of the screen, position it on
|
||||
// the left side of the cursor instead
|
||||
//
|
||||
// - If the tooltip overflows the top or bottom of the screen (with a bit of
|
||||
// margin), shift it just enough so that it is fully back inside the screen
|
||||
//
|
||||
useLayoutStyle(tooltipEl, ({offsetWidth, offsetHeight}) => {
|
||||
let dx = MARGIN_X
|
||||
let dy = 0 - offsetHeight / 2
|
||||
|
||||
if (absoluteHoverX + MARGIN_X + offsetWidth > window.innerWidth) {
|
||||
dx = 0 - MARGIN_X - offsetWidth
|
||||
}
|
||||
|
||||
if (absoluteHoverY + offsetHeight / 2 + MARGIN_Y > window.innerHeight) {
|
||||
dy = 0 - (absoluteHoverY + offsetHeight - window.innerHeight) - MARGIN_Y
|
||||
}
|
||||
|
||||
if (absoluteHoverY - offsetHeight / 2 - MARGIN_Y < 0) {
|
||||
dy = 0 - absoluteHoverY + MARGIN_Y
|
||||
}
|
||||
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: `${hoverX + dx}px`,
|
||||
top: `${hoverY + dy}px`,
|
||||
}
|
||||
})
|
||||
|
||||
if (!hoveredRowIndices) {
|
||||
if (!hoveredRowIndices || !tooltip) {
|
||||
return null
|
||||
}
|
||||
|
||||
const tooltipProps = getHistogramTooltipProps(layer, hoveredRowIndices)
|
||||
|
||||
return (
|
||||
<div className="minard-histogram-tooltip" ref={tooltipEl}>
|
||||
{/* TODO: Provide a default tooltip implementation */}
|
||||
{tooltip ? tooltip(tooltipProps) : null}
|
||||
</div>
|
||||
)
|
||||
return tooltip(getHistogramTooltipProps(layer, hoveredRowIndices))
|
||||
}
|
||||
|
||||
export default HistogramTooltip
|
||||
|
|
|
@ -19,6 +19,8 @@ export {
|
|||
TooltipProps as HistogramTooltipProps,
|
||||
} from 'src/minard/components/Histogram'
|
||||
|
||||
export {useTooltipStyle} from 'src/minard/utils/useTooltipStyle'
|
||||
|
||||
export {isNumeric} from 'src/minard/utils/isNumeric'
|
||||
|
||||
export type ColumnType = 'int' | 'uint' | 'float' | 'string' | 'time' | 'bool'
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import {useLayoutEffect, MutableRefObject, CSSProperties} from 'react'
|
||||
import {useLayoutEffect, CSSProperties} from 'react'
|
||||
|
||||
export const useLayoutStyle = (
|
||||
ref: MutableRefObject<HTMLElement>,
|
||||
el: HTMLElement,
|
||||
f: (el: HTMLElement) => CSSProperties
|
||||
) => {
|
||||
useLayoutEffect(() => {
|
||||
if (!ref.current) {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
|
||||
const style = f(ref.current)
|
||||
const style = f(el)
|
||||
|
||||
for (const [k, v] of Object.entries(style)) {
|
||||
ref.current.style[k] = v
|
||||
el.style[k] = v
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import {useLayoutStyle} from 'src/minard/utils/useLayoutStyle'
|
||||
import {useMousePos} from 'src/minard/utils/useMousePos'
|
||||
|
||||
const MARGIN_X = 15
|
||||
|
||||
export const useTooltipStyle = (el: HTMLDivElement) => {
|
||||
const {x, y} = useMousePos(document.body)
|
||||
|
||||
// Position the tooltip next to the mouse cursor, like this:
|
||||
//
|
||||
// ┌─────────────┐
|
||||
// │ │
|
||||
// (mouse) │ tooltip │
|
||||
// │ │
|
||||
// └─────────────┘
|
||||
//
|
||||
// The positioning is subject to the following restrictions:
|
||||
//
|
||||
// - If the tooltip overflows the right side of the screen, position it on
|
||||
// the left side of the cursor instead
|
||||
//
|
||||
// - If the tooltip overflows the top or bottom of the screen (with a bit of
|
||||
// margin), shift it just enough so that it is fully back inside the screen
|
||||
//
|
||||
useLayoutStyle(el, ({offsetWidth, offsetHeight}) => {
|
||||
let dx = MARGIN_X
|
||||
let dy = 0 - offsetHeight / 2
|
||||
|
||||
if (x + dx + offsetWidth > window.innerWidth) {
|
||||
dx = 0 - MARGIN_X - offsetWidth
|
||||
}
|
||||
|
||||
if (y + dy + offsetHeight > window.innerHeight) {
|
||||
dy -= y + dy + offsetHeight - window.innerHeight
|
||||
}
|
||||
|
||||
if (y + dy < 0) {
|
||||
dy += 0 - (y + dy)
|
||||
}
|
||||
|
||||
return {
|
||||
display: 'inline',
|
||||
position: 'fixed',
|
||||
left: `${x + dx}px`,
|
||||
top: `${y + dy}px`,
|
||||
}
|
||||
})
|
||||
}
|
|
@ -9,6 +9,8 @@
|
|||
padding: 10px;
|
||||
color: $g14-chromium;
|
||||
z-index: $z--dygraph-legend;
|
||||
position: fixed;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.histogram-tooltip--bin {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React, {SFC} from 'react'
|
||||
import React, {useRef, SFC} from 'react'
|
||||
import {createPortal} from 'react-dom'
|
||||
import {uniq, flatten} from 'lodash'
|
||||
import {HistogramTooltipProps} from 'src/minard'
|
||||
import {HistogramTooltipProps, useTooltipStyle} from 'src/minard'
|
||||
import {format} from 'd3-format'
|
||||
|
||||
import {TOOLTIP_PORTAL_ID} from 'src/shared/components/TooltipPortal'
|
||||
|
||||
import 'src/shared/components/HistogramTooltip.scss'
|
||||
|
||||
const formatLarge = format('.4~s')
|
||||
|
@ -10,12 +13,16 @@ const formatSmall = format('.4~g')
|
|||
const formatBin = n => (n < 1 && n > -1 ? formatSmall(n) : formatLarge(n))
|
||||
|
||||
const HistogramTooltip: SFC<HistogramTooltipProps> = ({xMin, xMax, counts}) => {
|
||||
const tooltipEl = useRef<HTMLDivElement>(null)
|
||||
|
||||
useTooltipStyle(tooltipEl.current)
|
||||
|
||||
const groupColNames = uniq(
|
||||
flatten(counts.map(({grouping}) => Object.keys(grouping)))
|
||||
).sort()
|
||||
|
||||
return (
|
||||
<div className="histogram-tooltip">
|
||||
return createPortal(
|
||||
<div className="histogram-tooltip" ref={tooltipEl}>
|
||||
<div className="histogram-tooltip--bin">
|
||||
{formatBin(xMin)} – {formatBin(xMax)}
|
||||
</div>
|
||||
|
@ -49,7 +56,8 @@ const HistogramTooltip: SFC<HistogramTooltipProps> = ({xMin, xMax, counts}) => {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.querySelector(`#${TOOLTIP_PORTAL_ID}`)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
|
||||
export const TOOLTIP_PORTAL_ID = 'tooltip-portal'
|
||||
|
||||
const TooltipPortal = () => {
|
||||
return <div id={TOOLTIP_PORTAL_ID} />
|
||||
}
|
||||
|
||||
export default TooltipPortal
|
Loading…
Reference in New Issue