Skip to content

Commit

Permalink
#6674 render vertical headers (#6919)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mariusz Jurowicz authored and scottdraves committed Mar 1, 2018
1 parent 9ab4bda commit 968e888
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 81 deletions.
39 changes: 24 additions & 15 deletions js/notebook/src/tableDisplay/dataGrid/BeakerxDataGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import {CellRenderer, DataGrid, DataModel} from "@phosphor/datagrid";
import {CellRenderer, DataGrid} from "@phosphor/datagrid";
import { BeakerxDataGridModel } from "./model/BeakerxDataGridModel";
import { Widget } from "@phosphor/widgets";
import { Signal } from '@phosphor/signaling';
Expand All @@ -30,19 +30,17 @@ import RowManager from "./row/RowManager";
import CellSelectionManager from "./cell/CellSelectionManager";
import CellManager from "./cell/CellManager";
import {DataGridHelpers} from "./dataGridHelpers";
import EventManager from "./EventManager";import {
IMessageHandler, Message
} from '@phosphor/messaging';

import getStringWidth = DataGridHelpers.getStringWidth;
import EventManager from "./EventManager";
import { IMessageHandler, Message } from '@phosphor/messaging';
import CellFocusManager from "./cell/CellFocusManager";
import findSectionIndex = DataGridHelpers.findSectionIndex;
import {
DEFAULT_GRID_BORDER_WIDTH,
DEFAULT_GRID_PADDING, DEFAULT_ROW_HEIGHT,
MIN_COLUMN_WIDTH
} from "./style/dataGridStyle";
import CellTooltipManager from "./cell/CellTooltipManager";
import findSectionIndex = DataGridHelpers.findSectionIndex;
import getStringSize = DataGridHelpers.getStringSize;

export class BeakerxDataGrid extends DataGrid {
columnSections: any;
Expand Down Expand Up @@ -138,7 +136,7 @@ export class BeakerxDataGrid extends DataGrid {
delta: column.delta,
type: COLUMN_TYPES.index,
offset: this.getColumnOffset(column.index, COLUMN_TYPES.index),
offsetTop: 0
offsetTop: this.headerHeight
};
}

Expand All @@ -164,7 +162,7 @@ export class BeakerxDataGrid extends DataGrid {
row: row ? row.index : 0,
type: columnType,
offset: this.getColumnOffset(column.index, columnType),
offsetTop: row ? this.getRowOffset(row.index) : 0
offsetTop: row ? this.getRowOffset(row.index) + this.headerHeight : 0
};
}

Expand Down Expand Up @@ -230,6 +228,7 @@ export class BeakerxDataGrid extends DataGrid {

this.addHighlighterManager(modelState);
this.addCellRenderers();
this.resizeHeader();
this.setWidgetHeight();
this.resizeSections();
}
Expand Down Expand Up @@ -292,16 +291,25 @@ export class BeakerxDataGrid extends DataGrid {
);
}

private resizeHeader() {
if (!this.model.state.headersVertical) { return; }

this.baseColumnHeaderSize = Math.max.apply(null, this.columnManager.bodyColumnNames.map(name => {
return getStringSize(name, this.model.state.headerFontSize).width;
}));
}

private getSectionWidth(column) {
const value = String(column.formatFn(this.cellManager.createCellConfig({
region: 'body',
value: column.maxValue,
column: column.index,
row: 0,
})));
const nameWidth = getStringWidth(column.name, this.model.state.headerFontSize);
const valueWidth = getStringWidth(value,this.model.state.dataFontSize);
const result = nameWidth > valueWidth ? nameWidth: valueWidth;
const nameSize = getStringSize(column.name, this.model.state.headerFontSize);
const valueSize = getStringSize(value,this.model.state.dataFontSize);
const nameSizeProp = this.model.state.headersVertical ? 'height' : 'width';
const result = nameSize[nameSizeProp] > valueSize.width ? nameSize[nameSizeProp] : valueSize.width;

return result > MIN_COLUMN_WIDTH ? result : MIN_COLUMN_WIDTH;
}
Expand All @@ -310,9 +318,10 @@ export class BeakerxDataGrid extends DataGrid {
const valueCharLength = this.model.rowCount('body');
const name = this.columnManager.getColumnByIndex(COLUMN_TYPES.index, 0).name;
const value = name.length > valueCharLength ? name : String(valueCharLength);
const nameWidth = getStringWidth(name, this.model.state.headerFontSize);
const valueWidth = getStringWidth(value, this.model.state.dataFontSize);
const result = nameWidth > valueWidth ? nameWidth: valueWidth;
const nameSizeProp = this.model.state.headersVertical ? 'height' : 'width';
const nameSize = getStringSize(name, this.model.state.headerFontSize);
const valueSize = getStringSize(value, this.model.state.dataFontSize);
const result = nameSize[nameSizeProp] > valueSize.width ? nameSize[nameSizeProp]: valueSize.width;

this.rowHeaderSections.resizeSection(0, result + 10);
}
Expand Down
1 change: 1 addition & 0 deletions js/notebook/src/tableDisplay/dataGrid/EventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default class EventManager {
this.dataGrid.headerCellHovered.emit(null);
this.dataGrid.node.classList.remove('bko-focused');
this.dataGrid.focused = false;
this.dataGrid.cellTooltipManager.hideTooltip();
enableKeyboardManager();
}

Expand Down
206 changes: 206 additions & 0 deletions js/notebook/src/tableDisplay/dataGrid/cell/BeakerxCellRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
* Copyright 2018 TWO SIGMA OPEN SOURCE, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DEFAULT_ALIGNMENT } from "../column/columnAlignment";
import {CellRenderer, GraphicsContext, TextRenderer} from "@phosphor/datagrid";
import { BeakerxDataGrid } from "../BeakerxDataGrid";
import DataGridCell from "./DataGridCell";
import {
darken,
DEFAULT_CELL_BACKGROUND,
DEFAULT_DATA_FONT_COLOR,
DEFAULT_DATA_FONT_SIZE,
DEFAULT_HEADER_FONT_COLOR, formatColor
} from "../style/dataGridStyle";

export default class BeakerxCellRenderer extends TextRenderer {
dataGrid: BeakerxDataGrid;
backgroundColor: CellRenderer.ConfigOption<string>;
horizontalAlignment: CellRenderer.ConfigOption<TextRenderer.HorizontalAlignment>;
format: TextRenderer.FormatFunc;
font: CellRenderer.ConfigOption<string>;
textColor: CellRenderer.ConfigOption<string>;

constructor(dataGrid: BeakerxDataGrid, options?: TextRenderer.IOptions) {
super(options);

this.dataGrid = dataGrid;
this.backgroundColor = this.getBackgroundColor.bind(this);
this.horizontalAlignment = this.getHorizontalAlignment.bind(this);
this.format = this.getFormat.bind(this);
this.font = this.getFont.bind(this);
this.textColor = this.getTextColor.bind(this);
}

getBackgroundColor(config: CellRenderer.ICellConfig): string {
if (DataGridCell.isHeaderCell(config)) {
return DEFAULT_CELL_BACKGROUND;
}

let selectionColor = this.dataGrid.cellSelectionManager.getBackgroundColor(config);
let highlighterColor = this.dataGrid.highlighterManager.getCellBackground(config);
let focusedColor = this.dataGrid.cellFocusManager.getFocussedCellBackground(config);
let initialColor = selectionColor && highlighterColor && darken(highlighterColor);

return focusedColor && initialColor && darken(initialColor) ||
focusedColor ||
initialColor ||
highlighterColor ||
selectionColor ||
DEFAULT_CELL_BACKGROUND;
}

getHorizontalAlignment(config: CellRenderer.ICellConfig): string {
let column = this.dataGrid.getColumn(config);

return column ? column.getAlignment() : DEFAULT_ALIGNMENT;
}

getFormat(config: CellRenderer.ICellConfig) {
let column = this.dataGrid.getColumn(config);

return DataGridCell.isHeaderCell(config) ? config.value : column.formatFn(config);
}

getFont({ region }): string {
let fontSize = (region === 'column-header' || region === 'corner-header')
? this.dataGrid.model.state.headerFontSize
: this.dataGrid.model.state.dataFontSize;

return `normal ${fontSize || DEFAULT_DATA_FONT_SIZE}px Lato, Helvetica, sans-serif`
}

getTextColor(config): string {
if (config.region === 'row-header') {
return DEFAULT_DATA_FONT_COLOR;
}

let colors = this.dataGrid.model.state.fontColor;
let dataFontColor = colors && colors[config.row]
? formatColor(colors[config.row][config.column])
: DEFAULT_DATA_FONT_COLOR;

return config.region === 'column-header' || config.region === "corner-header"
? DEFAULT_HEADER_FONT_COLOR
: dataFontColor;
}

drawText(gc: GraphicsContext, config: CellRenderer.ICellConfig): void {
// Resolve the font for the cell.
let font = CellRenderer.resolveOption(this.font, config);

// Bail if there is no font to draw.
if (!font) {
return;
}

// Resolve the text color for the cell.
let color = CellRenderer.resolveOption(this.textColor, config);

// Bail if there is no text color to draw.
if (!color) {
return;
}

// Format the cell value to text.
let format = this.format;
let text = format(config);

// Bail if there is no text to draw.
if (!text) {
return;
}

// Resolve the vertical and horizontal alignment.
let vAlign = CellRenderer.resolveOption(this.verticalAlignment, config);
let hAlign = CellRenderer.resolveOption(this.horizontalAlignment, config);

// Compute the padded text box height for the specified alignment.
let boxHeight = config.height - (vAlign === 'center' ? 1 : 2);

// Bail if the text box has no effective size.
if (boxHeight <= 0) {
return;
}

// Compute the text height for the gc font.
let textHeight = TextRenderer.measureFontHeight(font);

// Set up the text position variables.
let textX: number;
let textY: number;

// Compute the Y position for the text.
switch (vAlign) {
case 'top':
textY = config.y + 2 + textHeight;
break;
case 'center':
textY = config.y + config.height / 2 + textHeight / 2;
break;
case 'bottom':
textY = config.y + config.height - 2;
break;
default:
throw 'unreachable';
}

// Compute the X position for the text.
switch (hAlign) {
case 'left':
textX = config.x + 2;
break;
case 'center':
textX = config.x + config.width / 2;
break;
case 'right':
textX = config.x + config.width - 3;
break;
default:
throw 'unreachable';
}

// Clip the cell if the text is taller than the text box height.
if (textHeight > boxHeight) {
gc.beginPath();
gc.rect(config.x, config.y, config.width, config.height - 1);
gc.clip();
}

let verticalHeader = DataGridCell.isHeaderCell(config) && this.dataGrid.model.state.headersVertical;

// Set the gc state.
gc.textBaseline = 'bottom';
gc.textAlign = hAlign;

if(verticalHeader) {
gc.save();
gc.rotate(-Math.PI/2);

textX = -config.height + 2;
textY = config.x + config.width - 3;
gc.textBaseline = 'bottom';
gc.textAlign = 'left';
}

gc.font = font;
gc.fillStyle = color;

// Draw the text for the cell.
gc.fillText(text, textX, textY);
verticalHeader && gc.restore();
}
}
64 changes: 2 additions & 62 deletions js/notebook/src/tableDisplay/dataGrid/cell/CellRendererFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,8 @@
* limitations under the License.
*/

import { DEFAULT_ALIGNMENT } from "../column/columnAlignment";
import { CellRenderer, TextRenderer } from "@phosphor/datagrid";
import { BeakerxDataGrid } from "../BeakerxDataGrid";
import DataGridCell from "./DataGridCell";
import {
darken,
DEFAULT_CELL_BACKGROUND,
DEFAULT_DATA_FONT_COLOR,
DEFAULT_DATA_FONT_SIZE,
DEFAULT_HEADER_FONT_COLOR, formatColor
} from "../style/dataGridStyle";
import BeakerxCellRenderer from "./BeakerxCellRenderer";

export class CellRendererFactory {
private dataGrid: BeakerxDataGrid;
Expand All @@ -34,57 +25,6 @@ export class CellRendererFactory {
}

getRenderer() {
let self = this;

return new TextRenderer({
backgroundColor: (config: CellRenderer.ICellConfig) => {
if (DataGridCell.isHeaderCell(config)) {
return DEFAULT_CELL_BACKGROUND;
}

let selectionColor = self.dataGrid.cellSelectionManager.getBackgroundColor(config);
let highlighterColor = self.dataGrid.highlighterManager.getCellBackground(config);
let focusedColor = self.dataGrid.cellFocusManager.getFocussedCellBackground(config);
let initialColor = selectionColor && highlighterColor && darken(highlighterColor);

return focusedColor && initialColor && darken(initialColor) ||
focusedColor ||
initialColor ||
highlighterColor ||
selectionColor ||
DEFAULT_CELL_BACKGROUND;
},
horizontalAlignment: (config: CellRenderer.ICellConfig) => {
let column = self.dataGrid.getColumn(config);

return column ? column.getAlignment() : DEFAULT_ALIGNMENT;
},
format: (config: CellRenderer.ICellConfig) => {
let column = self.dataGrid.getColumn(config);

return DataGridCell.isHeaderCell(config) ? config.value : column.formatFn(config);
},
font: ({ region }) => {
let fontSize = (region === 'column-header' || region === 'corner-header')
? this.dataGrid.model.state.headerFontSize
: this.dataGrid.model.state.dataFontSize;

return `normal ${fontSize || DEFAULT_DATA_FONT_SIZE}px Lato, Helvetica, sans-serif`
},
textColor: (config) => {
if (config.region === 'row-header') {
return DEFAULT_DATA_FONT_COLOR;
}

let colors = this.dataGrid.model.state.fontColor;
let dataFontColor = colors && colors[config.row]
? formatColor(colors[config.row][config.column])
: DEFAULT_DATA_FONT_COLOR;

return config.region === 'column-header' || config.region === "corner-header"
? DEFAULT_HEADER_FONT_COLOR
: dataFontColor;
}
});
return new BeakerxCellRenderer(this.dataGrid);
}
}
Loading

0 comments on commit 968e888

Please sign in to comment.