UNPKG

rms-sparklines

Version:
273 lines (271 loc) 12.6 kB
/// <reference types="mathjs" /> /** * Introduction * Bar chart sparlines are a collection of rectangles, a.k.a. bars, drawn * alongside the canvas x-axis, with a small gap between them. * * Bar chart sparlines are charactized by their types: positive, negative, * dual, and tri. Positive charts' bars with positve heights are drawn * northward; bars with negative heights are cropped out. Negative charts' * bars with negative heights are drawn southward; bars with positive heights * are cropped out. Dual charts' bars heights are negative, zero, or positive, * with a positive height bars drawn northward and negative ones southward. * Tri charts have equal height bars, with positive drawn northward, zero * straddling the x-axis, and negative southward. * Bars are characterized by origin, height, width, gap, and fill color. The * origin is always (0,0). The height is provided, and scaled to fit within the * canvas. The width is calculated and its proportional number of bars and the * the canvas width. The gap is provided. The fill color is provided and used to * draw the bars' backgrounds. * * Calculation parameters * The parameters required to draw a bar char sparkline fall within two * categories: attributes, calculated. See below for their definitions and * descriptions. * * Bar width calculation * The bar width, barWidth, is the ratio between the canvas width and the * number of bars to draw, rounded down. The barWidth calculation is an * iteration seeking the number o bars to draw, barHeights.length, that yields * a barWidth equal or higher than the minimum bar width, minimumBarWidth. If * the initial barWidth, Math.floor(canvas.width / heights.length), yields a * barWidth that is less than minimumBarWidth, we drop the leftmost barHeights * element and recalculate the barWidth using the new heights.length; we * iterate until the barWidth is greater or equal to minBarWidth. * * Bar gap insertion * The bar gaps, barGap, inserted between the bars might cause rightmost bars to * be cropped. The barGap insertion is an iteration seeking for a * barHeights.length enabling barGaps to be inserted without cropping any * rightmost bars. It has two phases: reducing the barWidth and pruning * barHeights leftmost elements. * * If the initial width required, widthRequired, to insert the * bars and their gaps, * barWidth*barHeights.length + barGap*(barHeights.length - 1), is greater than * the canvas.width and the barWidth is greater than the minimumBarWidth, * decrement the barWidth by one pixel and recalculate * the widthRequired using the new barWidth; we iterate until the * widthRequired is less or equal to canvas.width. * * If the initial width required, widthRequired, to insert the * bars and their gaps, * barWidth*barHeights.length + barGap*(barHeights.length - 1), is greater than * the canvas.width and the barWidth is not greater than the minimumBarWidth, we * drop the leftmost barHeights element and recalculate the widthRequired using * the new heights.length; we iterate until the widthRequired is less or equal to * canvas.width. * 0 1 2 3 4 5 6 7 8 12345678901234567890123456789012345678901234567890123456789012345678901234567890 * Bar chart type drawing * The bar chart origin, direction, and scale calculations depend on the bar * chart type. We use the canvas setTransform and translate functions to aid in * setting them. We start by establishing the canvas default state and then * describe the operations to transform it to suit the bar char we are drawing * * In the canvas original state, its origin is top-let, positive drawing * southward, and negative northward. The canvas translate method is used to * move the origin; the canvas scale method is used to flip the canvas drawing * and to move the origin when inserting gaps. * * Wehn drawing a positive chart: * - the canvas origin is translated to the bottom-left * - the drawing is scaled to * - flip the drawing direction * - fit the barHeights into the canvas height. * * Wehn drawing a negative chart: * - the canvas origin remains at top-right * - the drawing is scaled to * - flip the drawing direction * - fit the barHeights into the canvas height. * * Wehn drawing a dual chart: * - the canvas origin is translated to the middle-left * - the drawing is scaled to * - flip the drawing direction * - fit the barHeights into half of the canvas height. * * Wehn drawing a tri chart: * - the canvas origin is translated to the middle-left * - the drawing is scaled to * - flip the drawing direction * - fit the barHeights into half of the canvas height. * - All bars are drawn with height = canvas.height / 2 * - zero heights are drawn half northward, and hald southward * * TODO: Add validation for color names ... ValidColors.ts */ import { Bar } from './bar'; import { Bar3d } from './bar-3d'; import { Matrix } from 'mathjs'; import { Rectangle } from './rectangle'; export declare class BarChart { canvasEl: HTMLCanvasElement; chartType: string; barHeights: number[]; minimumBarWidth: number; barGap: number; fillColorMinus: string; fillColorZero: string; fillColorPlus: string; ctx: CanvasRenderingContext2D; canvasWidth: number; canvasHeight: number; barWidth: number; bars: Bar[]; bars_3d: Bar3d[]; coordinatesTips: any; VALID_TYPES: string[]; POSITIVE: number; NEGATIVE: number; DUAL: number; TRI: number; tooltipId: string; private cssColorString; setCanvasEl(value: HTMLCanvasElement): void; getCanvasEl(): HTMLCanvasElement; setChartType(value: string): void; getChartType(): string; setBarHeights(value: number[]): void; getBarHeights(): number[]; setMinimumBarWidth(value: number): void; getMinimumBarWidth(): number; setBarGap(value: number): void; getBarGap(): number; setFillColorMinus(value: string): void; getFillColorMinus(): string; setFillColorZero(value: string): void; getFillColorZero(): string; setFillColorPlus(value: string): void; getFillColorPlus(): string; setCtx(value: CanvasRenderingContext2D): void; getCtx(): CanvasRenderingContext2D; setCanvasWidth(value: number): void; getCanvasWidth(): number; setCanvasHeight(value: number): void; getCanvasHeight(): number; setBarWidth(value: number): void; getBarWidth(): number; getBars(): Bar[]; setBars3D(value: Bar3d[]): void; getBars3D(): Bar3d[]; setCoordinatesTips(value: Rectangle[]): void; getCoordinatesTips(): Rectangle[]; constructor(canvasEl: HTMLCanvasElement, chartType: string, barHeights: number[], minimumBarWidth: number, barGap: number, fillColorMinus: string, fillColorZero: string, fillColorPlus: string); draw(): void; /** * The bar width, barWidth, is the ratio between the canvas width and the * number of bars to draw, rounded down. The barWidth calculation is an * iteration seeking the number o bars to draw, barHeights.length, that yields * a barWidth equal or higher than the minimum bar width, minimumBarWidth. If * the initial barWidth, Math.floor(canvas.width / heights.length), yields a * barWidth that is less than minimumBarWidth, we drop the leftmost barHeights * element and recalculate the barWidth using the new heights.length; we iterate * until the barWidth is greater or equal to minBarWidth. * * Ideally, all bars will fit in the canvas.width, when drawn with the * minBarWidth. Therefore, if maximum number of bars that can be drawn in the * canvas is equal of larger than the number of bars to be drawn, we are done. * Otherwise, if barHeights has more bars than can be draw and must be pruned * to the maximum number of bars that can be drawn in the canvas. * * maxNumberOfBarsThatCanBeDrawnOnCanvas = Math.floor(canvasWidth / minBarWidth) * * @param {number} canvasWidth * @param {number[]} barHeights * @param {number} minBarWidth * @returns {number[]} */ calculateBarWidth(canvasWidth: number, barHeights: number[], minBarWidth: number): number[]; computeBarWidth(canvasWidth: number, barHeights: number[]): number; /** * If the initial width required, widthRequired, to insert the * bars and their gaps, * barWidth*barHeights.length + barGap*(barHeights.length - 1), is greater than * the canvas.width and the barWidth is greater than the minimumBarWidth, * decrement the barWidth by one pixel and recalculate * the widthRequired using the new barWidth; we iterate until the * widthRequired is less or equal to canvas.width. * * * In essence, The iteration seeks to find the barWidth that solves the * following equation: * canvas.width <= barWidth * barHeights.length + barGap * (barHeights.length - 1) * * Resolving for barWidth.length, we get: * barWidth = Math.max(minimumBarWidth, Math.floor(canvas.width + barGap - barGap * barHeights.length) /barHeights.length)) * * barWidth = Math.max(minimumBarWidth, Math.floor( (canvasWidth - (barGap * (barHeights.length - 1))) / barHeights.length)); * * * @param {number} canvasWidth * @param {number[]} barHeights * @param {number} barGap * @param {number} minimumBarWidth * @returns {number} */ insertGapsUsingBarWidth(canvasWidth: number, barHeights: number[], barGap: number, minimumBarWidth: number): number; /** * If the initial width required, widthRequired, to insert the * bars and their gaps, * barWidth*barHeights.length + barGap*(barHeights.length - 1), is greater than * the canvas.width and the barWidth is not greater than the minimumBarWidth, we * drop the leftmost barHeights element and recalculate the widthRequired using * the new heights.length; we iterate until the widthRequired is less or equal to * canvas.width. * * In essence, The iteration seeks to find the barHeights.length that solves the * following equation: * canvas.width <= barHeights.length * barWidth + (barHeights.length - 1) * barGap * * Resolving for barHeights.length, we get: * barHeights.length = Math.floor((canvas.width + barGap) / (barWidth + barGap)) * * Given the desired barHeights.length, we prune barHeights to have the desired * length * * @param {number} canvasWidth * @param {number[]} barHeights * @param {number} barWidth * @param {number} barGap * @returns {number[]} */ insertGapsUsingBarHeights(canvasWidth: number, barHeights: number[], barWidth: number, barGap: number): number[]; buildBars(canvasHeight: number, barHeights: number[], barWidth: number, chartType: string, fillColorMinus: string, fillColorZero: string, fillColorPlus: string): Bar[]; buildBars_1(barGap: number, barHeights: number[], barWidth: number, chartType: string, fillColorMinus: string, fillColorZero: string, fillColorPlus: string): Bar3d[]; /** * Builds a collection of Bar3D bars, one for each barHeight * @param barGap * @param barHeights * @param barWidth * @param chartType * @param fillColorMinus * @param fillColorPlus * @param fillColorZero */ buildWorldCoordinateBars(barGap: number, barHeights: number[], barWidth: number, chartType: string, fillColorMinus: string, fillColorPlus: string, fillColorZero: string): Bar3d[]; /** * Scale the Y coordinates; note that the X coordinates were scaled during the calculations to establish the width and gap. * TODO: examine the possibility to switch all calculation to be done AFTER all transformations are done. * @param barHeights * @param chartType * @param canvasHeight */ scaleToCanvasHeight(barHeights: number[], chartType: string, canvasHeight: number): Matrix; /** * Calculate sFlipCanvasMatrix, to switch from world coordinates to canvas coordinates */ scaleToCanvasCoordinates(): Matrix; moveWithinCanvas(canvasHeight: number, chartType: string): Matrix; buildCoordinateTips(bard3D: Bar3d[]): Rectangle[]; _draw_1(ctx: CanvasRenderingContext2D, bars: Bar3d[]): void; /** * Sparkline Barchart Mouse Move Handler * @param $event * @param canvasEl */ handleMouseMove($event: MouseEvent, canvasEl: HTMLElement): void; handleMouseOut(): void; }