diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index 69829269864..36698f8fb09 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -72,7 +72,7 @@ export class BarGauge extends PureComponent { steps: [], }, }, - itemSpacing: 10, + itemSpacing: 8, showUnfilled: true, }; diff --git a/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx b/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx index 326d53cd028..9380a75720f 100644 --- a/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx +++ b/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx @@ -1,5 +1,6 @@ import React, { PureComponent, CSSProperties } from 'react'; import { VizOrientation } from '@grafana/data'; +import { calculateGridDimensions } from '../../utils/squares'; interface Props { /** @@ -21,6 +22,8 @@ interface Props { renderCounter: number; // force update of values & render orientation: VizOrientation; itemSpacing?: number; + /** When orientation is set to auto layout items in a grid */ + autoGrid?: boolean; } export interface VizRepeaterRenderValueProps { @@ -43,7 +46,7 @@ interface State { export class VizRepeater extends PureComponent, State> { static defaultProps: DefaultProps = { - itemSpacing: 10, + itemSpacing: 8, }; constructor(props: Props) { @@ -75,10 +78,61 @@ export class VizRepeater extends PureComponent, State> return orientation; } - render() { - const { renderValue, height, width, itemSpacing, getAlignmentFactors } = this.props as PropsWithDefaults; + renderGrid() { + const { renderValue, height, width, itemSpacing, getAlignmentFactors, orientation } = this + .props as PropsWithDefaults; + const { values } = this.state; - const orientation = this.getOrientation(); + const grid = calculateGridDimensions(width, height, itemSpacing, values.length); + const alignmentFactors = getAlignmentFactors ? getAlignmentFactors(values, grid.width, grid.height) : ({} as D); + + let xGrid = 0; + let yGrid = 0; + let items: JSX.Element[] = []; + + for (let i = 0; i < values.length; i++) { + const value = values[i]; + const isLastRow = yGrid === grid.yCount - 1; + + const itemWidth = isLastRow ? grid.widthOnLastRow : grid.width; + const itemHeight = grid.height; + + const xPos = xGrid * itemWidth + itemSpacing * xGrid; + const yPos = yGrid * itemHeight + itemSpacing * yGrid; + + const itemStyles: CSSProperties = { + position: 'absolute', + left: xPos, + top: yPos, + width: `${itemWidth}px`, + height: `${itemHeight}px`, + }; + + items.push( +
+ {renderValue({ value, width: itemWidth, height: itemHeight, alignmentFactors, orientation })} +
+ ); + + xGrid++; + + if (xGrid === grid.xCount) { + xGrid = 0; + yGrid++; + } + } + + return
{items}
; + } + + render() { + const { renderValue, height, width, itemSpacing, getAlignmentFactors, autoGrid, orientation } = this + .props as PropsWithDefaults; + const { values } = this.state; + + if (autoGrid && orientation === VizOrientation.Auto) { + return this.renderGrid(); + } const itemStyles: React.CSSProperties = { display: 'flex', @@ -91,17 +145,19 @@ export class VizRepeater extends PureComponent, State> let vizHeight = height; let vizWidth = width; - if (orientation === VizOrientation.Horizontal) { - repeaterStyle.flexDirection = 'column'; - itemStyles.marginBottom = `${itemSpacing}px`; - vizWidth = width; - vizHeight = height / values.length - itemSpacing + itemSpacing / values.length; - } else { - repeaterStyle.flexDirection = 'row'; - repeaterStyle.justifyContent = 'space-between'; - itemStyles.marginRight = `${itemSpacing}px`; - vizHeight = height; - vizWidth = width / values.length - itemSpacing + itemSpacing / values.length; + switch (this.getOrientation()) { + case VizOrientation.Horizontal: + repeaterStyle.flexDirection = 'column'; + itemStyles.marginBottom = `${itemSpacing}px`; + vizWidth = width; + vizHeight = height / values.length - itemSpacing + itemSpacing / values.length; + break; + case VizOrientation.Vertical: + repeaterStyle.flexDirection = 'row'; + repeaterStyle.justifyContent = 'space-between'; + itemStyles.marginRight = `${itemSpacing}px`; + vizHeight = height; + vizWidth = width / values.length - itemSpacing + itemSpacing / values.length; } itemStyles.width = `${vizWidth}px`; diff --git a/packages/grafana-ui/src/utils/squares.ts b/packages/grafana-ui/src/utils/squares.ts new file mode 100644 index 00000000000..3b57185bb45 --- /dev/null +++ b/packages/grafana-ui/src/utils/squares.ts @@ -0,0 +1,45 @@ +/** + * This function will calculate how many squares we can fit inside a rectangle. + * Please have a look at this post for more details about the implementation: + * https://math.stackexchange.com/questions/466198/algorithm-to-get-the-maximum-size-of-n-squares-that-fit-into-a-rectangle-with-a + * + * @param parentWidth width of the parent container + * @param parentHeight height of the parent container + * @param numberOfChildren number of children that should fit in the parent container + */ +export const calculateGridDimensions = ( + parentWidth: number, + parentHeight: number, + itemSpacing: number, + numberOfChildren: number +) => { + const vertical = calculateSizeOfChild(parentWidth, parentHeight, numberOfChildren); + const horizontal = calculateSizeOfChild(parentHeight, parentWidth, numberOfChildren); + const square = Math.max(vertical, horizontal); + let xCount = Math.floor(parentWidth / square); + let yCount = Math.ceil(numberOfChildren / xCount); + + // after yCount update xCount to make split between rows more even + xCount = Math.ceil(numberOfChildren / yCount); + + const itemsOnLastRow = xCount - (xCount * yCount - numberOfChildren); + const widthOnLastRow = parentWidth / itemsOnLastRow - itemSpacing + itemSpacing / itemsOnLastRow; + + return { + width: parentWidth / xCount - itemSpacing + itemSpacing / xCount, + height: parentHeight / yCount - itemSpacing + itemSpacing / yCount, + widthOnLastRow, + xCount, + yCount, + }; +}; + +function calculateSizeOfChild(parentWidth: number, parentHeight: number, numberOfChildren: number): number { + const parts = Math.ceil(Math.sqrt((numberOfChildren * parentWidth) / parentHeight)); + + if (Math.floor((parts * parentHeight) / parentWidth) * parts < numberOfChildren) { + return parentHeight / Math.ceil((parts * parentHeight) / parentWidth); + } + + return parentWidth / parts; +} diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 6873bc967db..3496b7fa924 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -60,6 +60,7 @@ export class GaugePanel extends PureComponent> { width={width} height={height} source={data} + autoGrid={true} renderCounter={renderCounter} orientation={VizOrientation.Auto} /> diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index 90bb403a49f..47f2da9821e 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -22,7 +22,6 @@ import { ReducerID, getDisplayValueAlignmentFactors, DisplayValueAlignmentFactors, - VizOrientation, } from '@grafana/data'; import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; @@ -97,23 +96,9 @@ export class StatPanel extends PureComponent> { height={height} source={data} renderCounter={renderCounter} - orientation={getOrientation(width, height, options.orientation)} + autoGrid={true} + orientation={options.orientation} /> ); } } - -/** - * Stat panel custom auto orientation - */ -function getOrientation(width: number, height: number, orientation: VizOrientation): VizOrientation { - if (orientation !== VizOrientation.Auto) { - return orientation; - } - - if (width / height > 2) { - return VizOrientation.Vertical; - } else { - return VizOrientation.Horizontal; - } -}