rms-sparklines
Version:
A sparkline collection, written as web components
273 lines (271 loc) • 12.6 kB
TypeScript
/// <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;
}