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:
parent
5ccefed7d7
commit
dea91c1fd6
@ -72,7 +72,7 @@ export class BarGauge extends PureComponent<Props> {
|
||||
steps: [],
|
||||
},
|
||||
},
|
||||
itemSpacing: 10,
|
||||
itemSpacing: 8,
|
||||
showUnfilled: true,
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import { VizOrientation } from '@grafana/data';
|
||||
import { calculateGridDimensions } from '../../utils/squares';
|
||||
|
||||
interface Props<V, D> {
|
||||
/**
|
||||
@ -21,6 +22,8 @@ interface Props<V, D> {
|
||||
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<V, D = {}> {
|
||||
@ -43,7 +46,7 @@ interface State<V> {
|
||||
|
||||
export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>> {
|
||||
static defaultProps: DefaultProps = {
|
||||
itemSpacing: 10,
|
||||
itemSpacing: 8,
|
||||
};
|
||||
|
||||
constructor(props: Props<V, D>) {
|
||||
@ -75,10 +78,61 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
|
||||
return orientation;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderValue, height, width, itemSpacing, getAlignmentFactors } = this.props as PropsWithDefaults<V, D>;
|
||||
renderGrid() {
|
||||
const { renderValue, height, width, itemSpacing, getAlignmentFactors, orientation } = this
|
||||
.props as PropsWithDefaults<V, D>;
|
||||
|
||||
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 = {
|
||||
display: 'flex',
|
||||
@ -91,17 +145,19 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
|
||||
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`;
|
||||
|
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}
|
||||
height={height}
|
||||
source={data}
|
||||
autoGrid={true}
|
||||
renderCounter={renderCounter}
|
||||
orientation={VizOrientation.Auto}
|
||||
/>
|
||||
|
@ -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<PanelProps<StatPanelOptions>> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user