mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
rotate!
This commit is contained in:
parent
b80c773ebe
commit
91a2307b98
@ -19,17 +19,26 @@ const replaceVariables = (value: string, scopedVars?: ScopedVars) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
export function makeDummyTable(columnCount: number, rowCount: number): TableData {
|
||||
export function columnIndexToLeter(column: number) {
|
||||
const A = 'A'.charCodeAt(0);
|
||||
const c1 = Math.floor(column / 26);
|
||||
const c2 = column % 26;
|
||||
if (c1 > 0) {
|
||||
return String.fromCharCode(A + c1 - 1) + String.fromCharCode(A + c2);
|
||||
}
|
||||
return String.fromCharCode(A + c2);
|
||||
}
|
||||
|
||||
export function makeDummyTable(columnCount: number, rowCount: number): TableData {
|
||||
return {
|
||||
columns: Array.from(new Array(columnCount), (x, i) => {
|
||||
return {
|
||||
text: String.fromCharCode(A + i),
|
||||
text: columnIndexToLeter(i),
|
||||
};
|
||||
}),
|
||||
rows: Array.from(new Array(rowCount), (x, rowId) => {
|
||||
const suffix = (rowId + 1).toString();
|
||||
return Array.from(new Array(columnCount), (x, colId) => String.fromCharCode(A + colId) + suffix);
|
||||
return Array.from(new Array(columnCount), (x, colId) => columnIndexToLeter(colId) + suffix);
|
||||
}),
|
||||
type: 'table',
|
||||
columnMap: {},
|
||||
@ -37,45 +46,54 @@ export function makeDummyTable(columnCount: number, rowCount: number): TableData
|
||||
}
|
||||
|
||||
storiesOf('Alpha/Table', module)
|
||||
.add('basic', () => {
|
||||
.add('Basic Table', () => {
|
||||
// NOTE: This example does not seem to survice rotate &
|
||||
// Changing fixed headers... but the next one does?
|
||||
// perhaps `simpleTable` is static and reused?
|
||||
|
||||
const showHeader = boolean('Show Header', true);
|
||||
const fixedRowCount = number('Fixed Rows', 1, { min: 0, max: 50, step: 1, range: false });
|
||||
const fixedColumnCount = number('Fixed Columns', 1, { min: 0, max: 50, step: 1, range: false });
|
||||
const fixedHeader = boolean('Fixed Header', true);
|
||||
const fixedColumns = number('Fixed Columns', 0, { min: 0, max: 50, step: 1, range: false });
|
||||
const rotate = boolean('Rotate', false);
|
||||
|
||||
return withFullSizeStory(Table, {
|
||||
styles: [],
|
||||
data: simpleTable,
|
||||
replaceVariables,
|
||||
fixedRowCount,
|
||||
fixedColumnCount,
|
||||
showHeader,
|
||||
fixedHeader,
|
||||
fixedColumns,
|
||||
rotate,
|
||||
theme: getTheme(GrafanaThemeType.Light),
|
||||
});
|
||||
})
|
||||
.add('variable size', () => {
|
||||
const columnCount = number('Column Count', 20, { min: 2, max: 50, step: 1, range: false });
|
||||
.add('Variable Size', () => {
|
||||
const columnCount = number('Column Count', 15, { min: 2, max: 50, step: 1, range: false });
|
||||
const rowCount = number('Row Count', 20, { min: 0, max: 100, step: 1, range: false });
|
||||
|
||||
const showHeader = boolean('Show Header', true);
|
||||
const fixedRowCount = number('Fixed Rows', 1, { min: 0, max: 50, step: 1, range: false });
|
||||
const fixedColumnCount = number('Fixed Columns', 1, { min: 0, max: 50, step: 1, range: false });
|
||||
const fixedHeader = boolean('Fixed Header', true);
|
||||
const fixedColumns = number('Fixed Columns', 1, { min: 0, max: 50, step: 1, range: false });
|
||||
const rotate = boolean('Rotate', false);
|
||||
|
||||
return withFullSizeStory(Table, {
|
||||
styles: [],
|
||||
data: makeDummyTable(columnCount, rowCount),
|
||||
replaceVariables,
|
||||
fixedRowCount,
|
||||
fixedColumnCount,
|
||||
showHeader,
|
||||
fixedHeader,
|
||||
fixedColumns,
|
||||
rotate,
|
||||
theme: getTheme(GrafanaThemeType.Light),
|
||||
});
|
||||
})
|
||||
.add('Old tests configuration', () => {
|
||||
.add('Test Config (migrated)', () => {
|
||||
return withFullSizeStory(Table, {
|
||||
styles: migratedTestStyles,
|
||||
data: migratedTestTable,
|
||||
replaceVariables,
|
||||
showHeader: true,
|
||||
rotate: true,
|
||||
theme: getTheme(GrafanaThemeType.Light),
|
||||
});
|
||||
});
|
||||
|
@ -14,21 +14,24 @@ import { Themeable } from '../../types/theme';
|
||||
import { sortTableData } from '../../utils/processTimeSeries';
|
||||
|
||||
import { TableData, InterpolateFunction } from '@grafana/ui';
|
||||
import { TableCellBuilder, ColumnStyle, getCellBuilder, TableCellBuilderOptions } from './TableCellBuilder';
|
||||
import {
|
||||
TableCellBuilder,
|
||||
ColumnStyle,
|
||||
getCellBuilder,
|
||||
TableCellBuilderOptions,
|
||||
simpleCellBuilder,
|
||||
} from './TableCellBuilder';
|
||||
import { stringToJsRegex } from '../../utils/index';
|
||||
|
||||
interface ColumnInfo {
|
||||
index: number;
|
||||
header: string;
|
||||
builder: TableCellBuilder;
|
||||
}
|
||||
|
||||
export interface Props extends Themeable {
|
||||
data: TableData;
|
||||
|
||||
showHeader: boolean;
|
||||
fixedColumnCount: number;
|
||||
fixedRowCount: number;
|
||||
fixedHeader: boolean;
|
||||
fixedColumns: number;
|
||||
rotate: boolean;
|
||||
styles: ColumnStyle[];
|
||||
|
||||
replaceVariables: InterpolateFunction;
|
||||
width: number;
|
||||
height: number;
|
||||
@ -41,15 +44,26 @@ interface State {
|
||||
data: TableData;
|
||||
}
|
||||
|
||||
interface ColumnRenderInfo {
|
||||
header: string;
|
||||
builder: TableCellBuilder;
|
||||
}
|
||||
|
||||
interface DataIndex {
|
||||
column: number;
|
||||
row: number; // -1 is the header!
|
||||
}
|
||||
|
||||
export class Table extends Component<Props, State> {
|
||||
columns: ColumnInfo[];
|
||||
renderer: ColumnRenderInfo[];
|
||||
measurer: CellMeasurerCache;
|
||||
scrollToTop = false;
|
||||
|
||||
static defaultProps = {
|
||||
showHeader: true,
|
||||
fixedRowCount: 1,
|
||||
fixedColumnCount: 0,
|
||||
fixedHeader: true,
|
||||
fixedColumns: 0,
|
||||
rotate: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
@ -59,7 +73,7 @@ export class Table extends Component<Props, State> {
|
||||
data: props.data,
|
||||
};
|
||||
|
||||
this.columns = this.initColumns(props);
|
||||
this.renderer = this.initColumns(props);
|
||||
this.measurer = new CellMeasurerCache({
|
||||
defaultHeight: 30,
|
||||
defaultWidth: 150,
|
||||
@ -70,9 +84,11 @@ export class Table extends Component<Props, State> {
|
||||
const { data, styles, showHeader } = this.props;
|
||||
const { sortBy, sortDirection } = this.state;
|
||||
const dataChanged = data !== prevProps.data;
|
||||
const configsChanged = showHeader !== prevProps.showHeader;
|
||||
|
||||
console.log('TABLE', this.props.theme);
|
||||
const configsChanged =
|
||||
showHeader !== prevProps.showHeader ||
|
||||
this.props.rotate !== prevProps.rotate ||
|
||||
this.props.fixedColumns !== prevProps.fixedColumns ||
|
||||
this.props.fixedHeader !== prevProps.fixedHeader;
|
||||
|
||||
// Reset the size cache
|
||||
if (dataChanged || configsChanged) {
|
||||
@ -80,8 +96,9 @@ export class Table extends Component<Props, State> {
|
||||
}
|
||||
|
||||
// Update the renderer if options change
|
||||
// We only *need* do to this if the header values changes, but this does every data update
|
||||
if (dataChanged || styles !== prevProps.styles) {
|
||||
this.columns = this.initColumns(this.props);
|
||||
this.renderer = this.initColumns(this.props);
|
||||
}
|
||||
|
||||
// Update the data when data or sort changes
|
||||
@ -91,9 +108,9 @@ export class Table extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
initColumns(props: Props): ColumnInfo[] {
|
||||
/** Given the configuration, setup how each column gets rendered */
|
||||
initColumns(props: Props): ColumnRenderInfo[] {
|
||||
const { styles, data } = props;
|
||||
console.log('STYLES', styles);
|
||||
|
||||
return data.columns.map((col, index) => {
|
||||
let title = col.text;
|
||||
@ -113,7 +130,6 @@ export class Table extends Component<Props, State> {
|
||||
}
|
||||
|
||||
return {
|
||||
index,
|
||||
header: title,
|
||||
builder: getCellBuilder(col, style, this.props),
|
||||
};
|
||||
@ -137,27 +153,37 @@ export class Table extends Component<Props, State> {
|
||||
this.setState({ sortBy: sort, sortDirection: dir });
|
||||
};
|
||||
|
||||
handleCellClick = (rowIndex: number, columnIndex: number) => {
|
||||
const { showHeader } = this.props;
|
||||
const { data } = this.state;
|
||||
const realRowIndex = rowIndex - (showHeader ? 1 : 0);
|
||||
if (realRowIndex < 0) {
|
||||
this.doSort(columnIndex);
|
||||
/** Converts the grid coordinates to TableData coordinates */
|
||||
getCellRef = (rowIndex: number, columnIndex: number): DataIndex => {
|
||||
const { showHeader, rotate } = this.props;
|
||||
const rowOffset = showHeader ? -1 : 0;
|
||||
|
||||
if (rotate) {
|
||||
return { column: rowIndex, row: columnIndex + rowOffset };
|
||||
} else {
|
||||
const row = data.rows[realRowIndex];
|
||||
const value = row[columnIndex];
|
||||
console.log('CLICK', rowIndex, columnIndex, value);
|
||||
return { column: columnIndex, row: rowIndex + rowOffset };
|
||||
}
|
||||
};
|
||||
|
||||
handleCellClick = (rowIndex: number, columnIndex: number) => {
|
||||
const { row, column } = this.getCellRef(rowIndex, columnIndex);
|
||||
if (row < 0) {
|
||||
this.doSort(column);
|
||||
} else {
|
||||
const values = this.state.data.rows[row];
|
||||
const value = values[column];
|
||||
console.log('CLICK', value, row);
|
||||
}
|
||||
};
|
||||
|
||||
headerBuilder = (cell: TableCellBuilderOptions): ReactElement<'div'> => {
|
||||
const { data, sortBy, sortDirection } = this.state;
|
||||
const { columnIndex, rowIndex, style } = cell.props;
|
||||
const { column } = this.getCellRef(rowIndex, columnIndex);
|
||||
|
||||
let col = data.columns[columnIndex];
|
||||
const sorting = sortBy === columnIndex;
|
||||
let col = data.columns[column];
|
||||
const sorting = sortBy === column;
|
||||
if (!col) {
|
||||
// NOT SURE Why this happens sometimes
|
||||
col = {
|
||||
text: '??' + columnIndex + '???',
|
||||
};
|
||||
@ -171,47 +197,60 @@ export class Table extends Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
getTableCellBuilder = (column: number): TableCellBuilder => {
|
||||
const render = this.renderer[column];
|
||||
if (render && render.builder) {
|
||||
return render.builder;
|
||||
}
|
||||
return simpleCellBuilder; // the default
|
||||
};
|
||||
|
||||
cellRenderer = (props: GridCellProps): React.ReactNode => {
|
||||
const { rowIndex, columnIndex, key, parent } = props;
|
||||
const { showHeader } = this.props;
|
||||
const { row, column } = this.getCellRef(rowIndex, columnIndex);
|
||||
const { data } = this.state;
|
||||
|
||||
const column = this.columns[columnIndex];
|
||||
if (!column) {
|
||||
// NOT SURE HOW/WHY THIS HAPPENS!
|
||||
// Without it it will crash in storybook when you cycle up/down the # of columns
|
||||
// this cell is never visible in the output?
|
||||
return (
|
||||
<div key={key} style={props.style}>
|
||||
XXXXX
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const realRowIndex = rowIndex - (showHeader ? 1 : 0);
|
||||
const isHeader = realRowIndex < 0;
|
||||
const row = isHeader ? data.columns : data.rows[realRowIndex];
|
||||
const value = row[columnIndex];
|
||||
const builder = isHeader ? this.headerBuilder : column.builder;
|
||||
const isHeader = row < 0;
|
||||
const rowData = isHeader ? data.columns : data.rows[row];
|
||||
const value = rowData ? rowData[column] : `[${columnIndex}:${rowIndex}]`;
|
||||
const builder = isHeader ? this.headerBuilder : this.getTableCellBuilder(column);
|
||||
|
||||
return (
|
||||
<CellMeasurer cache={this.measurer} columnIndex={columnIndex} key={key} parent={parent} rowIndex={rowIndex}>
|
||||
{builder({ value, row, table: this, props })}
|
||||
{builder({
|
||||
value,
|
||||
row: rowData,
|
||||
column: data.columns[column],
|
||||
table: this,
|
||||
props,
|
||||
})}
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, showHeader, width, height } = this.props;
|
||||
const { showHeader, fixedHeader, fixedColumns, rotate, width, height } = this.props;
|
||||
const { data } = this.state;
|
||||
|
||||
const columnCount = data.columns.length;
|
||||
const rowCount = data.rows.length + (showHeader ? 1 : 0);
|
||||
let columnCount = data.columns.length;
|
||||
let rowCount = data.rows.length + (showHeader ? 1 : 0);
|
||||
|
||||
const fixedColumnCount = Math.min(this.props.fixedColumnCount, columnCount);
|
||||
const fixedRowCount = Math.min(this.props.fixedRowCount, rowCount);
|
||||
let fixedColumnCount = Math.min(fixedColumns, columnCount);
|
||||
let fixedRowCount = showHeader && fixedHeader ? 1 : 0;
|
||||
|
||||
// Usually called after a sort or the data changes
|
||||
const scrollToRow = this.scrollToTop ? 1 : -1;
|
||||
if (rotate) {
|
||||
const temp = columnCount;
|
||||
columnCount = rowCount;
|
||||
rowCount = temp;
|
||||
|
||||
fixedRowCount = 0;
|
||||
fixedColumnCount = Math.min(fixedColumns, rowCount) + (showHeader && fixedHeader ? 1 : 0);
|
||||
}
|
||||
|
||||
// Called after sort or the data changes
|
||||
const scroll = this.scrollToTop ? 1 : -1;
|
||||
const scrollToRow = rotate ? -1 : scroll;
|
||||
const scrollToColumn = rotate ? scroll : -1;
|
||||
if (this.scrollToTop) {
|
||||
this.scrollToTop = false;
|
||||
}
|
||||
@ -226,9 +265,10 @@ export class Table extends Component<Props, State> {
|
||||
}
|
||||
scrollToRow={scrollToRow}
|
||||
columnCount={columnCount}
|
||||
scrollToColumn={scrollToColumn}
|
||||
rowCount={rowCount}
|
||||
overscanColumnCount={2}
|
||||
overscanRowCount={2}
|
||||
overscanColumnCount={8}
|
||||
overscanRowCount={8}
|
||||
columnWidth={this.measurer.columnWidth}
|
||||
deferredMeasurementCache={this.measurer}
|
||||
cellRenderer={this.cellRenderer}
|
||||
|
@ -11,6 +11,7 @@ import { InterpolateFunction } from '../../types/panel';
|
||||
|
||||
export interface TableCellBuilderOptions {
|
||||
value: any;
|
||||
column?: Column;
|
||||
row?: any[];
|
||||
table?: Table;
|
||||
className?: string;
|
||||
|
@ -163,5 +163,5 @@ export const migratedTestStyles: ColumnStyle[] = [
|
||||
export const simpleTable = {
|
||||
type: 'table',
|
||||
columns: [{ text: 'First' }, { text: 'Second' }, { text: 'Third' }],
|
||||
rows: [[10, 23, 35], [11, 22, 31], [12, 21, 34]],
|
||||
} as TableData;
|
||||
rows: [[701, 205, 305], [702, 206, 301], [703, 207, 304]],
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import _ from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { PanelEditorProps, Switch } from '@grafana/ui';
|
||||
import { PanelEditorProps, Switch, FormField } from '@grafana/ui';
|
||||
import { Options } from './types';
|
||||
|
||||
export class TablePanelEditor extends PureComponent<PanelEditorProps<Options>> {
|
||||
@ -11,14 +11,43 @@ export class TablePanelEditor extends PureComponent<PanelEditorProps<Options>> {
|
||||
this.props.onOptionsChange({ ...this.props.options, showHeader: !this.props.options.showHeader });
|
||||
};
|
||||
|
||||
onToggleFixedHeader = () => {
|
||||
this.props.onOptionsChange({ ...this.props.options, fixedHeader: !this.props.options.fixedHeader });
|
||||
};
|
||||
|
||||
onToggleRotate = () => {
|
||||
this.props.onOptionsChange({ ...this.props.options, rotate: !this.props.options.rotate });
|
||||
};
|
||||
|
||||
onFixedColumnsChange = ({ target }) => {
|
||||
this.props.onOptionsChange({ ...this.props.options, fixedColumns: target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showHeader } = this.props.options;
|
||||
const { showHeader, fixedHeader, rotate, fixedColumns } = this.props.options;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="section gf-form-group">
|
||||
<h5 className="section-heading">Header</h5>
|
||||
<Switch label="Show" labelClass="width-5" checked={showHeader} onChange={this.onToggleShowHeader} />
|
||||
<Switch label="Show" labelClass="width-6" checked={showHeader} onChange={this.onToggleShowHeader} />
|
||||
<Switch label="Fixed" labelClass="width-6" checked={fixedHeader} onChange={this.onToggleFixedHeader} />
|
||||
</div>
|
||||
|
||||
<div className="section gf-form-group">
|
||||
<h5 className="section-heading">Display</h5>
|
||||
<Switch label="Rotate" labelClass="width-8" checked={rotate} onChange={this.onToggleRotate} />
|
||||
<FormField
|
||||
label="Fixed Columns"
|
||||
labelWidth={8}
|
||||
inputWidth={4}
|
||||
type="number"
|
||||
step="1"
|
||||
min="0"
|
||||
max="100"
|
||||
onChange={this.onFixedColumnsChange}
|
||||
value={fixedColumns}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,11 +2,18 @@ import { ColumnStyle } from '@grafana/ui/src/components/Table/TableCellBuilder';
|
||||
|
||||
export interface Options {
|
||||
showHeader: boolean;
|
||||
fixedHeader: boolean;
|
||||
fixedColumns: number;
|
||||
rotate: boolean;
|
||||
|
||||
styles: ColumnStyle[];
|
||||
}
|
||||
|
||||
export const defaults: Options = {
|
||||
showHeader: true,
|
||||
fixedHeader: true,
|
||||
fixedColumns: 0,
|
||||
rotate: false,
|
||||
styles: [
|
||||
{
|
||||
type: 'date',
|
||||
|
Loading…
Reference in New Issue
Block a user