From 27227ebddceee461a3b22e432bf6d589df8a49e4 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 13 Mar 2019 15:20:43 -0700 Subject: [PATCH] feat: add tooltip and layout components for charts (#13) * feat: add components for charts * refactor: remove jsx * refactor: address comments --- .../src/components/ChartFrame.jsx | 50 ------------ .../src/components/ChartFrame.tsx | 47 +++++++++++ .../{WithLegend.jsx => WithLegend.tsx} | 77 +++++++++---------- .../src/components/tooltip/TooltipFrame.tsx | 26 +++++++ .../src/components/tooltip/TooltipTable.tsx | 39 ++++++++++ 5 files changed, 149 insertions(+), 90 deletions(-) delete mode 100644 plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.jsx create mode 100644 plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.tsx rename plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/{WithLegend.jsx => WithLegend.tsx} (68%) create mode 100644 plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipFrame.tsx create mode 100644 plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.jsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.jsx deleted file mode 100644 index fac6eb65a2..0000000000 --- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { isDefined } from '@superset-ui/core'; - -const propTypes = { - contentHeight: PropTypes.number, - contentWidth: PropTypes.number, - height: PropTypes.number.isRequired, - renderContent: PropTypes.func, - width: PropTypes.number.isRequired, -}; -const defaultProps = { - contentHeight: null, - contentWidth: null, - renderContent() {}, -}; - -class ChartFrame extends React.PureComponent { - render() { - const { contentWidth, contentHeight, width, height, renderContent } = this.props; - - const overflowX = isDefined(contentWidth) && contentWidth > width; - const overflowY = isDefined(contentHeight) && contentHeight > height; - - if (overflowX || overflowY) { - return ( -
- {renderContent({ - height: contentHeight, - width: contentWidth, - })} -
- ); - } - - return renderContent({ height, width }); - } -} - -ChartFrame.propTypes = propTypes; -ChartFrame.defaultProps = defaultProps; - -export default ChartFrame; diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.tsx new file mode 100644 index 0000000000..29974570bf --- /dev/null +++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/ChartFrame.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { isDefined } from '@superset-ui/core'; + +function checkNumber(input: any): input is number { + return isDefined(input) && typeof input === 'number'; +} + +type Props = { + contentWidth?: number; + contentHeight?: number; + height: number; + renderContent: ({ height, width }: { height: number; width: number }) => React.ReactElement; + width: number; +}; + +export default class ChartFrame extends React.PureComponent { + static defaultProps = { + renderContent() {}, + }; + + render() { + const { contentWidth, contentHeight, width, height, renderContent } = this.props; + + const overflowX = checkNumber(contentWidth) && contentWidth > width; + const overflowY = checkNumber(contentHeight) && contentHeight > height; + + if (overflowX || overflowY) { + return ( +
+ {renderContent({ + height: Math.max(contentHeight || 0, height), + width: Math.max(contentWidth || 0, width), + })} +
+ ); + } + + return renderContent({ height, width }); + } +} diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/WithLegend.jsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/WithLegend.tsx similarity index 68% rename from plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/WithLegend.jsx rename to plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/WithLegend.tsx index bdbf075214..bac3829925 100644 --- a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/WithLegend.jsx +++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/WithLegend.tsx @@ -17,30 +17,23 @@ * under the License. */ /* eslint-disable sort-keys */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { CSSProperties, ReactNode } from 'react'; import { ParentSize } from '@vx/responsive'; +// eslint-disable-next-line import/no-unresolved +import * as CSS from 'csstype'; -const propTypes = { - className: PropTypes.string, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - legendJustifyContent: PropTypes.oneOf(['center', 'flex-start', 'flex-end']), - position: PropTypes.oneOf(['top', 'left', 'bottom', 'right']), - renderChart: PropTypes.func.isRequired, - renderLegend: PropTypes.func.isRequired, - hideLegend: PropTypes.bool, -}; -const defaultProps = { - className: '', - width: 'auto', - height: 'auto', - legendJustifyContent: undefined, - position: 'top', - hideLegend: false, +type Props = { + className: string; + width: number | string; + height: number | string; + legendJustifyContent: 'center' | 'flex-start' | 'flex-end'; + position: 'top' | 'left' | 'bottom' | 'right'; + renderChart: (dim: { width: number; height: number }) => ReactNode; + renderLegend: (params: { direction: string }) => ReactNode; + hideLegend: boolean; }; -const LEGEND_STYLE_BASE = { +const LEGEND_STYLE_BASE: CSSProperties = { display: 'flex', flexGrow: 0, flexShrink: 0, @@ -49,15 +42,24 @@ const LEGEND_STYLE_BASE = { fontSize: '0.9em', }; -const CHART_STYLE_BASE = { +const CHART_STYLE_BASE: CSSProperties = { flexGrow: 1, flexShrink: 1, flexBasis: 'auto', position: 'relative', }; -class WithLegend extends React.Component { - getContainerDirection() { +class WithLegend extends React.PureComponent { + static defaultProps = { + className: '', + width: 'auto', + height: 'auto', + legendJustifyContent: undefined, + position: 'top', + hideLegend: false, + }; + + getContainerDirection(): CSS.FlexDirectionProperty { const { position } = this.props; switch (position) { case 'left': @@ -101,13 +103,9 @@ class WithLegend extends React.Component { hideLegend, } = this.props; - if (hideLegend) { - return
{renderChart({ width, height })}
; - } - const isHorizontal = position === 'left' || position === 'right'; - const style = { + const style: CSSProperties = { display: 'flex', flexDirection: this.getContainerDirection(), }; @@ -118,7 +116,7 @@ class WithLegend extends React.Component { style.height = height; } - const chartStyle = { ...CHART_STYLE_BASE }; + const chartStyle: CSSProperties = { ...CHART_STYLE_BASE }; if (isHorizontal) { chartStyle.width = 0; } else { @@ -126,7 +124,7 @@ class WithLegend extends React.Component { } const legendDirection = isHorizontal ? 'column' : 'row'; - const legendStyle = { + const legendStyle: CSSProperties = { ...LEGEND_STYLE_BASE, flexDirection: legendDirection, justifyContent: this.getLegendJustifyContent(), @@ -134,15 +132,17 @@ class WithLegend extends React.Component { return (
-
- {renderLegend({ - // Pass flexDirection for @vx/legend to arrange legend items - direction: legendDirection, - })} -
+ {!hideLegend && ( +
+ {renderLegend({ + // Pass flexDirection for @vx/legend to arrange legend items + direction: legendDirection, + })} +
+ )}
- {parent => + {(parent: { width: number; height: number }) => parent.width > 0 && parent.height > 0 ? // Only render when necessary renderChart(parent) @@ -155,7 +155,4 @@ class WithLegend extends React.Component { } } -WithLegend.propTypes = propTypes; -WithLegend.defaultProps = defaultProps; - export default WithLegend; diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipFrame.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipFrame.tsx new file mode 100644 index 0000000000..a37df9ab38 --- /dev/null +++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipFrame.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +type Props = { + className?: string; + children: React.ReactNode; +}; + +const CONTAINER_STYLE = { padding: 8 }; + +class TooltipFrame extends React.PureComponent { + static defaultProps = { + className: '', + }; + + render() { + const { className, children } = this.props; + + return ( +
+ {children} +
+ ); + } +} + +export default TooltipFrame; diff --git a/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx new file mode 100644 index 0000000000..aeee7f88ad --- /dev/null +++ b/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/tooltip/TooltipTable.tsx @@ -0,0 +1,39 @@ +import React, { CSSProperties } from 'react'; + +type Props = { + className?: string; + data: { + key: string; + keyStyle?: CSSProperties; + value: string | number; + valueStyle?: CSSProperties; + }[]; +}; + +const VALUE_CELL_STYLE: CSSProperties = { paddingLeft: 8, textAlign: 'right' }; + +export default class TooltipTable extends React.PureComponent { + static defaultProps = { + className: '', + data: [], + }; + + render() { + const { className, data } = this.props; + + return ( + + + {data.map(({ key, keyStyle, value, valueStyle }) => ( + + + + + ))} + +
{key} + {value} +
+ ); + } +}