mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Panel: added possibility to group plugins in a panel according to a grid. (#22028)
* POC grid layout of gague. * added a grid property that can be used to auto size the charts within a panel. * fill the grid * fix lint * change default for stat panel * avoid empty cells * Moved to absolute positioning * Fixed spacing * Another fix * Improve layout algorithm * VizRepeater: Reverted back to so this is auto behavior for some panels Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
@@ -72,7 +72,7 @@ export class BarGauge extends PureComponent<Props> {
|
|||||||
steps: [],
|
steps: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
itemSpacing: 10,
|
itemSpacing: 8,
|
||||||
showUnfilled: true,
|
showUnfilled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { PureComponent, CSSProperties } from 'react';
|
import React, { PureComponent, CSSProperties } from 'react';
|
||||||
import { VizOrientation } from '@grafana/data';
|
import { VizOrientation } from '@grafana/data';
|
||||||
|
import { calculateGridDimensions } from '../../utils/squares';
|
||||||
|
|
||||||
interface Props<V, D> {
|
interface Props<V, D> {
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +22,8 @@ interface Props<V, D> {
|
|||||||
renderCounter: number; // force update of values & render
|
renderCounter: number; // force update of values & render
|
||||||
orientation: VizOrientation;
|
orientation: VizOrientation;
|
||||||
itemSpacing?: number;
|
itemSpacing?: number;
|
||||||
|
/** When orientation is set to auto layout items in a grid */
|
||||||
|
autoGrid?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VizRepeaterRenderValueProps<V, D = {}> {
|
export interface VizRepeaterRenderValueProps<V, D = {}> {
|
||||||
@@ -43,7 +46,7 @@ interface State<V> {
|
|||||||
|
|
||||||
export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>> {
|
export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>> {
|
||||||
static defaultProps: DefaultProps = {
|
static defaultProps: DefaultProps = {
|
||||||
itemSpacing: 10,
|
itemSpacing: 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: Props<V, D>) {
|
constructor(props: Props<V, D>) {
|
||||||
@@ -75,10 +78,61 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
|
|||||||
return orientation;
|
return orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderGrid() {
|
||||||
const { renderValue, height, width, itemSpacing, getAlignmentFactors } = this.props as PropsWithDefaults<V, D>;
|
const { renderValue, height, width, itemSpacing, getAlignmentFactors, orientation } = this
|
||||||
|
.props as PropsWithDefaults<V, D>;
|
||||||
|
|
||||||
const { values } = this.state;
|
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(
|
||||||
|
<div key={i} style={itemStyles}>
|
||||||
|
{renderValue({ value, width: itemWidth, height: itemHeight, alignmentFactors, orientation })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
xGrid++;
|
||||||
|
|
||||||
|
if (xGrid === grid.xCount) {
|
||||||
|
xGrid = 0;
|
||||||
|
yGrid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div style={{ position: 'relative' }}>{items}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { renderValue, height, width, itemSpacing, getAlignmentFactors, autoGrid, orientation } = this
|
||||||
|
.props as PropsWithDefaults<V, D>;
|
||||||
|
const { values } = this.state;
|
||||||
|
|
||||||
|
if (autoGrid && orientation === VizOrientation.Auto) {
|
||||||
|
return this.renderGrid();
|
||||||
|
}
|
||||||
|
|
||||||
const itemStyles: React.CSSProperties = {
|
const itemStyles: React.CSSProperties = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -91,17 +145,19 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
|
|||||||
let vizHeight = height;
|
let vizHeight = height;
|
||||||
let vizWidth = width;
|
let vizWidth = width;
|
||||||
|
|
||||||
if (orientation === VizOrientation.Horizontal) {
|
switch (this.getOrientation()) {
|
||||||
repeaterStyle.flexDirection = 'column';
|
case VizOrientation.Horizontal:
|
||||||
itemStyles.marginBottom = `${itemSpacing}px`;
|
repeaterStyle.flexDirection = 'column';
|
||||||
vizWidth = width;
|
itemStyles.marginBottom = `${itemSpacing}px`;
|
||||||
vizHeight = height / values.length - itemSpacing + itemSpacing / values.length;
|
vizWidth = width;
|
||||||
} else {
|
vizHeight = height / values.length - itemSpacing + itemSpacing / values.length;
|
||||||
repeaterStyle.flexDirection = 'row';
|
break;
|
||||||
repeaterStyle.justifyContent = 'space-between';
|
case VizOrientation.Vertical:
|
||||||
itemStyles.marginRight = `${itemSpacing}px`;
|
repeaterStyle.flexDirection = 'row';
|
||||||
vizHeight = height;
|
repeaterStyle.justifyContent = 'space-between';
|
||||||
vizWidth = width / values.length - itemSpacing + itemSpacing / values.length;
|
itemStyles.marginRight = `${itemSpacing}px`;
|
||||||
|
vizHeight = height;
|
||||||
|
vizWidth = width / values.length - itemSpacing + itemSpacing / values.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
itemStyles.width = `${vizWidth}px`;
|
itemStyles.width = `${vizWidth}px`;
|
||||||
|
|||||||
45
packages/grafana-ui/src/utils/squares.ts
Normal file
45
packages/grafana-ui/src/utils/squares.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
|||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
source={data}
|
source={data}
|
||||||
|
autoGrid={true}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
orientation={VizOrientation.Auto}
|
orientation={VizOrientation.Auto}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
ReducerID,
|
ReducerID,
|
||||||
getDisplayValueAlignmentFactors,
|
getDisplayValueAlignmentFactors,
|
||||||
DisplayValueAlignmentFactors,
|
DisplayValueAlignmentFactors,
|
||||||
VizOrientation,
|
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||||
@@ -97,23 +96,9 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
|||||||
height={height}
|
height={height}
|
||||||
source={data}
|
source={data}
|
||||||
renderCounter={renderCounter}
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user