diff --git a/ui/src/minard/components/Plot.tsx b/ui/src/minard/components/Plot.tsx index 8facd5fa97..e677c2a65c 100644 --- a/ui/src/minard/components/Plot.tsx +++ b/ui/src/minard/components/Plot.tsx @@ -1,132 +1,34 @@ -import React, {useReducer, useRef, useMemo, SFC, CSSProperties} from 'react' +import React, {SFC} from 'react' +import {AutoSizer} from 'react-virtualized' -import {Table, PlotEnv} from 'src/minard' -import {Axes} from 'src/minard/components/Axes' -import {useMousePos} from 'src/minard/utils/useMousePos' -import {useMountedEffect} from 'src/minard/utils/useMountedEffect' import { - setDimensions, - setTable, - setControlledXDomain, - setControlledYDomain, - setXAxisLabel, - setYAxisLabel, -} from 'src/minard/utils/plotEnvActions' -import {plotEnvReducer, INITIAL_PLOT_ENV} from 'src/minard/utils/plotEnvReducer' + SizedPlot, + Props as SizedPlotProps, +} from 'src/minard/components/SizedPlot' -export interface Props { - // - // Required props - // ============== - // - table: Table - width: number - height: number - children: (env: PlotEnv) => JSX.Element +type Props = Pick< + SizedPlotProps, + Exclude +> & {width?: number; height?: number} - // - // Miscellaneous options - // ===================== - // - axesStroke?: string - tickFont?: string - tickFill?: string - xAxisLabel?: string - yAxisLabel?: string - - // The x domain of the plot can be explicitly set. If this prop is passed, - // then the component is operating in a "controlled" mode, where it always - // uses the passed x domain. Any interaction with the plot that should change - // the x domain (clicking, brushing, etc.) will call the `onSetXDomain` prop - // when the component is in controlled mode. If the `xDomain` prop is not - // passed, then the component is "uncontrolled". It will compute and set the - // `xDomain` automatically. - xDomain?: [number, number] - onSetXDomain?: (xDomain: [number, number]) => void - - // See the `xDomain` and `onSetXDomain` props - yDomain?: [number, number] - onSetYDomain?: (yDomain: [number, number]) => void -} - -export const Plot: SFC = ({ - width, - height, - table, - children, - axesStroke = '#31313d', - tickFont = 'bold 10px Roboto', - tickFill = '#8e91a1', - xAxisLabel = '', - yAxisLabel = '', - xDomain = null, - yDomain = null, -}) => { - const [env, dispatch] = useReducer(plotEnvReducer, { - ...INITIAL_PLOT_ENV, - width, - height, - xDomain, - yDomain, - xAxisLabel, - yAxisLabel, - baseLayer: {...INITIAL_PLOT_ENV.baseLayer, table}, - }) - - useMountedEffect(() => dispatch(setTable(table)), [table]) - useMountedEffect(() => dispatch(setControlledXDomain(xDomain)), [xDomain]) - useMountedEffect(() => dispatch(setControlledYDomain(yDomain)), [yDomain]) - useMountedEffect(() => dispatch(setXAxisLabel(xAxisLabel)), [xAxisLabel]) - useMountedEffect(() => dispatch(setYAxisLabel(yAxisLabel)), [yAxisLabel]) - useMountedEffect(() => dispatch(setDimensions(width, height)), [ - width, - height, - ]) - - const mouseRegion = useRef(null) - const {x: hoverX, y: hoverY} = useMousePos(mouseRegion.current) - - const childProps = useMemo( - () => ({ - ...env, - hoverX, - hoverY, - dispatch, - }), - [env, hoverX, hoverY, dispatch] - ) - - const plotStyle: CSSProperties = { - position: 'relative', - width: `${width}px`, - height: `${height}px`, - } - - const layersStyle: CSSProperties = { - position: 'absolute', - top: `${env.margins.top}px`, - right: `${env.margins.right}px`, - bottom: `${env.margins.bottom}px`, - left: `${env.margins.left}px`, +/* + Works just like a `SizedPlot`, except it will measure the width and height of + the containing element if no `width` and `height` props are passed. +*/ +export const Plot: SFC = props => { + if (props.width && props.height) { + return } return ( -
- -
- {children(childProps)} -
-
- -
+ + {({width, height}) => { + if (width === 0 || height === 0) { + return null + } + + return + }} + ) } diff --git a/ui/src/minard/components/SizedPlot.tsx b/ui/src/minard/components/SizedPlot.tsx new file mode 100644 index 0000000000..fa4fbe2de8 --- /dev/null +++ b/ui/src/minard/components/SizedPlot.tsx @@ -0,0 +1,132 @@ +import React, {useReducer, useRef, useMemo, SFC, CSSProperties} from 'react' + +import {Table, PlotEnv} from 'src/minard' +import {Axes} from 'src/minard/components/Axes' +import {useMousePos} from 'src/minard/utils/useMousePos' +import {useMountedEffect} from 'src/minard/utils/useMountedEffect' +import { + setDimensions, + setTable, + setControlledXDomain, + setControlledYDomain, + setXAxisLabel, + setYAxisLabel, +} from 'src/minard/utils/plotEnvActions' +import {plotEnvReducer, INITIAL_PLOT_ENV} from 'src/minard/utils/plotEnvReducer' + +export interface Props { + // + // Required props + // ============== + // + table: Table + width: number + height: number + children: (env: PlotEnv) => JSX.Element + + // + // Miscellaneous options + // ===================== + // + axesStroke?: string + tickFont?: string + tickFill?: string + xAxisLabel?: string + yAxisLabel?: string + + // The x domain of the plot can be explicitly set. If this prop is passed, + // then the component is operating in a "controlled" mode, where it always + // uses the passed x domain. Any interaction with the plot that should change + // the x domain (clicking, brushing, etc.) will call the `onSetXDomain` prop + // when the component is in controlled mode. If the `xDomain` prop is not + // passed, then the component is "uncontrolled". It will compute and set the + // `xDomain` automatically. + xDomain?: [number, number] + onSetXDomain?: (xDomain: [number, number]) => void + + // See the `xDomain` and `onSetXDomain` props + yDomain?: [number, number] + onSetYDomain?: (yDomain: [number, number]) => void +} + +export const SizedPlot: SFC = ({ + width, + height, + table, + children, + axesStroke = '#31313d', + tickFont = 'bold 10px Roboto', + tickFill = '#8e91a1', + xAxisLabel = '', + yAxisLabel = '', + xDomain = null, + yDomain = null, +}) => { + const [env, dispatch] = useReducer(plotEnvReducer, { + ...INITIAL_PLOT_ENV, + width, + height, + xDomain, + yDomain, + xAxisLabel, + yAxisLabel, + baseLayer: {...INITIAL_PLOT_ENV.baseLayer, table}, + }) + + useMountedEffect(() => dispatch(setTable(table)), [table]) + useMountedEffect(() => dispatch(setControlledXDomain(xDomain)), [xDomain]) + useMountedEffect(() => dispatch(setControlledYDomain(yDomain)), [yDomain]) + useMountedEffect(() => dispatch(setXAxisLabel(xAxisLabel)), [xAxisLabel]) + useMountedEffect(() => dispatch(setYAxisLabel(yAxisLabel)), [yAxisLabel]) + useMountedEffect(() => dispatch(setDimensions(width, height)), [ + width, + height, + ]) + + const mouseRegion = useRef(null) + const {x: hoverX, y: hoverY} = useMousePos(mouseRegion.current) + + const childProps = useMemo( + () => ({ + ...env, + hoverX, + hoverY, + dispatch, + }), + [env, hoverX, hoverY, dispatch] + ) + + const plotStyle: CSSProperties = { + position: 'relative', + width: `${width}px`, + height: `${height}px`, + } + + const layersStyle: CSSProperties = { + position: 'absolute', + top: `${env.margins.top}px`, + right: `${env.margins.right}px`, + bottom: `${env.margins.bottom}px`, + left: `${env.margins.left}px`, + } + + return ( +
+ +
+ {children(childProps)} +
+
+ +
+ ) +} diff --git a/ui/src/shared/components/Histogram.tsx b/ui/src/shared/components/Histogram.tsx index d3f3092702..03c036eb2d 100644 --- a/ui/src/shared/components/Histogram.tsx +++ b/ui/src/shared/components/Histogram.tsx @@ -1,7 +1,6 @@ // Libraries import React, {useMemo, useEffect, SFC} from 'react' import {connect} from 'react-redux' -import {AutoSizer} from 'react-virtualized' import { Plot as MinardPlot, Histogram as MinardHistogram, @@ -99,36 +98,24 @@ const Histogram: SFC = ({ } return ( - - {({width, height}) => { - if (width === 0 || height === 0) { - return null - } - - return ( - - {env => ( - - )} - - ) - }} - + + {env => ( + + )} + ) }