Commit 91a2307b by ryan

rotate!

parent b80c773e
......@@ -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 });
};
/** 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 {
return { column: columnIndex, row: rowIndex + rowOffset };
}
};
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);
const { row, column } = this.getCellRef(rowIndex, columnIndex);
if (row < 0) {
this.doSort(column);
} else {
const row = data.rows[realRowIndex];
const value = row[columnIndex];
console.log('CLICK', rowIndex, columnIndex, value);
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;
if (rotate) {
const temp = columnCount;
columnCount = rowCount;
rowCount = temp;
fixedRowCount = 0;
fixedColumnCount = Math.min(fixedColumns, rowCount) + (showHeader && fixedHeader ? 1 : 0);
}
// Usually called after a sort or the data changes
const scrollToRow = this.scrollToTop ? 1 : -1;
// 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',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment