x-data-spreadsheet
Version:
a javascript xpreadsheet
369 lines (340 loc) • 10.5 kB
JavaScript
import { stringAt } from '../core/alphabet';
import { getFontSizePxByPt } from '../core/font';
import _cell from '../core/cell';
import { formulam } from '../core/formula';
import { formatm } from '../core/format';
import {
Draw, DrawBox, thinLineWidth, npx,
} from '../canvas/draw';
// gobal var
const cellPaddingWidth = 5;
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
const tableGridStyle = {
fillStyle: '#fff',
lineWidth: thinLineWidth,
strokeStyle: '#e6e6e6',
};
function tableFixedHeaderStyle() {
return {
textAlign: 'center',
textBaseline: 'middle',
font: `500 ${npx(12)}px Source Sans Pro`,
fillStyle: '#585757',
lineWidth: thinLineWidth(),
strokeStyle: '#e6e6e6',
};
}
function getDrawBox(data, rindex, cindex, yoffset = 0) {
const {
left, top, width, height,
} = data.cellRect(rindex, cindex);
return new DrawBox(left, top + yoffset, width, height, cellPaddingWidth);
}
/*
function renderCellBorders(bboxes, translateFunc) {
const { draw } = this;
if (bboxes) {
const rset = new Set();
// console.log('bboxes:', bboxes);
bboxes.forEach(({ ri, ci, box }) => {
if (!rset.has(ri)) {
rset.add(ri);
translateFunc(ri);
}
draw.strokeBorders(box);
});
}
}
*/
export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
const { sortedRowMap, rows, cols } = data;
if (rows.isHide(rindex) || cols.isHide(cindex)) return;
let nrindex = rindex;
if (sortedRowMap.has(rindex)) {
nrindex = sortedRowMap.get(rindex);
}
const cell = data.getCell(nrindex, cindex);
if (cell === null) return;
let frozen = false;
if ('editable' in cell && cell.editable === false) {
frozen = true;
}
const style = data.getCellStyleOrDefault(nrindex, cindex);
const dbox = getDrawBox(data, rindex, cindex, yoffset);
dbox.bgcolor = style.bgcolor;
if (style.border !== undefined) {
dbox.setBorders(style.border);
// bboxes.push({ ri: rindex, ci: cindex, box: dbox });
draw.strokeBorders(dbox);
}
draw.rect(dbox, () => {
// render text
let cellText = _cell.render(cell.text || '', formulam, (y, x) => (data.getCellTextOrDefault(x, y)));
if (style.format) {
// console.log(data.formatm, '>>', cell.format);
cellText = formatm[style.format].render(cellText);
}
const font = Object.assign({}, style.font);
font.size = getFontSizePxByPt(font.size);
// console.log('style:', style);
draw.text(cellText, dbox, {
align: style.align,
valign: style.valign,
font,
color: style.color,
strike: style.strike,
underline: style.underline,
}, style.textwrap);
// error
const error = data.validations.getError(rindex, cindex);
if (error) {
// console.log('error:', rindex, cindex, error);
draw.error(dbox);
}
if (frozen) {
draw.frozen(dbox);
}
});
}
function renderAutofilter(viewRange) {
const { data, draw } = this;
if (viewRange) {
const { autoFilter } = data;
if (!autoFilter.active()) return;
const afRange = autoFilter.hrange();
if (viewRange.intersects(afRange)) {
afRange.each((ri, ci) => {
const dbox = getDrawBox(data, ri, ci);
draw.dropdown(dbox);
});
}
}
}
function renderContent(viewRange, fw, fh, tx, ty) {
const { draw, data } = this;
draw.save();
draw.translate(fw, fh)
.translate(tx, ty);
const { exceptRowSet } = data;
// const exceptRows = Array.from(exceptRowSet);
const filteredTranslateFunc = (ri) => {
const ret = exceptRowSet.has(ri);
if (ret) {
const height = data.rows.getHeight(ri);
draw.translate(0, -height);
}
return !ret;
};
const exceptRowTotalHeight = data.exceptRowTotalHeight(viewRange.sri, viewRange.eri);
// 1 render cell
draw.save();
draw.translate(0, -exceptRowTotalHeight);
viewRange.each((ri, ci) => {
renderCell(draw, data, ri, ci);
}, ri => filteredTranslateFunc(ri));
draw.restore();
// 2 render mergeCell
const rset = new Set();
draw.save();
draw.translate(0, -exceptRowTotalHeight);
data.eachMergesInView(viewRange, ({ sri, sci, eri }) => {
if (!exceptRowSet.has(sri)) {
renderCell(draw, data, sri, sci);
} else if (!rset.has(sri)) {
rset.add(sri);
const height = data.rows.sumHeight(sri, eri + 1);
draw.translate(0, -height);
}
});
draw.restore();
// 3 render autofilter
renderAutofilter.call(this, viewRange);
draw.restore();
}
function renderSelectedHeaderCell(x, y, w, h) {
const { draw } = this;
draw.save();
draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' })
.fillRect(x, y, w, h);
draw.restore();
}
// viewRange
// type: all | left | top
// w: the fixed width of header
// h: the fixed height of header
// tx: moving distance on x-axis
// ty: moving distance on y-axis
function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
const { draw, data } = this;
const sumHeight = viewRange.h; // rows.sumHeight(viewRange.sri, viewRange.eri + 1);
const sumWidth = viewRange.w; // cols.sumWidth(viewRange.sci, viewRange.eci + 1);
const nty = ty + h;
const ntx = tx + w;
draw.save();
// draw rect background
draw.attr(tableFixedHeaderCleanStyle);
if (type === 'all' || type === 'left') draw.fillRect(0, nty, w, sumHeight);
if (type === 'all' || type === 'top') draw.fillRect(ntx, 0, sumWidth, h);
const {
sri, sci, eri, eci,
} = data.selector.range;
// console.log(data.selectIndexes);
// draw text
// text font, align...
draw.attr(tableFixedHeaderStyle());
// y-header-text
if (type === 'all' || type === 'left') {
data.rowEach(viewRange.sri, viewRange.eri, (i, y1, rowHeight) => {
const y = nty + y1;
const ii = i;
draw.line([0, y], [w, y]);
if (sri <= ii && ii < eri + 1) {
renderSelectedHeaderCell.call(this, 0, y, w, rowHeight);
}
draw.fillText(ii + 1, w / 2, y + (rowHeight / 2));
if (i > 0 && data.rows.isHide(i - 1)) {
draw.save();
draw.attr({ strokeStyle: '#c6c6c6' });
draw.line([5, y + 5], [w - 5, y + 5]);
draw.restore();
}
});
draw.line([0, sumHeight + nty], [w, sumHeight + nty]);
draw.line([w, nty], [w, sumHeight + nty]);
}
// x-header-text
if (type === 'all' || type === 'top') {
data.colEach(viewRange.sci, viewRange.eci, (i, x1, colWidth) => {
const x = ntx + x1;
const ii = i;
draw.line([x, 0], [x, h]);
if (sci <= ii && ii < eci + 1) {
renderSelectedHeaderCell.call(this, x, 0, colWidth, h);
}
draw.fillText(stringAt(ii), x + (colWidth / 2), h / 2);
if (i > 0 && data.cols.isHide(i - 1)) {
draw.save();
draw.attr({ strokeStyle: '#c6c6c6' });
draw.line([x + 5, 5], [x + 5, h - 5]);
draw.restore();
}
});
draw.line([sumWidth + ntx, 0], [sumWidth + ntx, h]);
draw.line([0, h], [sumWidth + ntx, h]);
}
draw.restore();
}
function renderFixedLeftTopCell(fw, fh) {
const { draw } = this;
draw.save();
// left-top-cell
draw.attr({ fillStyle: '#f4f5f8' })
.fillRect(0, 0, fw, fh);
draw.restore();
}
function renderContentGrid({
sri, sci, eri, eci, w, h,
}, fw, fh, tx, ty) {
const { draw, data } = this;
const { settings } = data;
draw.save();
draw.attr(tableGridStyle)
.translate(fw + tx, fh + ty);
// const sumWidth = cols.sumWidth(sci, eci + 1);
// const sumHeight = rows.sumHeight(sri, eri + 1);
// console.log('sumWidth:', sumWidth);
draw.clearRect(0, 0, w, h);
if (!settings.showGrid) {
draw.restore();
return;
}
// console.log('rowStart:', rowStart, ', rowLen:', rowLen);
data.rowEach(sri, eri, (i, y, ch) => {
// console.log('y:', y);
if (i !== sri) draw.line([0, y], [w, y]);
if (i === eri) draw.line([0, y + ch], [w, y + ch]);
});
data.colEach(sci, eci, (i, x, cw) => {
if (i !== sci) draw.line([x, 0], [x, h]);
if (i === eci) draw.line([x + cw, 0], [x + cw, h]);
});
draw.restore();
}
function renderFreezeHighlightLine(fw, fh, ftw, fth) {
const { draw, data } = this;
const twidth = data.viewWidth() - fw;
const theight = data.viewHeight() - fh;
draw.save()
.translate(fw, fh)
.attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
draw.line([0, fth], [twidth, fth]);
draw.line([ftw, 0], [ftw, theight]);
draw.restore();
}
/** end */
class Table {
constructor(el, data) {
this.el = el;
this.draw = new Draw(el, data.viewWidth(), data.viewHeight());
this.data = data;
}
resetData(data) {
this.data = data;
this.render();
}
render() {
// resize canvas
const { data } = this;
const { rows, cols } = data;
// fixed width of header
const fw = cols.indexWidth;
// fixed height of header
const fh = rows.height;
this.draw.resize(data.viewWidth(), data.viewHeight());
this.clear();
const viewRange = data.viewRange();
// renderAll.call(this, viewRange, data.scroll);
const tx = data.freezeTotalWidth();
const ty = data.freezeTotalHeight();
const { x, y } = data.scroll;
// 1
renderContentGrid.call(this, viewRange, fw, fh, tx, ty);
renderContent.call(this, viewRange, fw, fh, -x, -y);
renderFixedHeaders.call(this, 'all', viewRange, fw, fh, tx, ty);
renderFixedLeftTopCell.call(this, fw, fh);
const [fri, fci] = data.freeze;
if (fri > 0 || fci > 0) {
// 2
if (fri > 0) {
const vr = viewRange.clone();
vr.sri = 0;
vr.eri = fri - 1;
vr.h = ty;
renderContentGrid.call(this, vr, fw, fh, tx, 0);
renderContent.call(this, vr, fw, fh, -x, 0);
renderFixedHeaders.call(this, 'top', vr, fw, fh, tx, 0);
}
// 3
if (fci > 0) {
const vr = viewRange.clone();
vr.sci = 0;
vr.eci = fci - 1;
vr.w = tx;
renderContentGrid.call(this, vr, fw, fh, 0, ty);
renderFixedHeaders.call(this, 'left', vr, fw, fh, 0, ty);
renderContent.call(this, vr, fw, fh, 0, -y);
}
// 4
const freezeViewRange = data.freezeViewRange();
renderContentGrid.call(this, freezeViewRange, fw, fh, 0, 0);
renderFixedHeaders.call(this, 'all', freezeViewRange, fw, fh, 0, 0);
renderContent.call(this, freezeViewRange, fw, fh, 0, 0);
// 5
renderFreezeHighlightLine.call(this, fw, fh, tx, ty);
}
}
clear() {
this.draw.clear();
}
}
export default Table;