Skip to content

Commit

Permalink
axes: more customizations
Browse files Browse the repository at this point in the history
  • Loading branch information
cnoelle committed Jan 2, 2023
1 parent bf440cd commit 7c5e800
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 33 deletions.
12 changes: 9 additions & 3 deletions LineUtils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export interface LabelConfig {
size?: number;
position?: LabelPosition;
lineOffsetFactor?: number;
rotated?: boolean;
/**
* Default: false. If set to true, the label will be rotated with the same angle as the axis against a horizontal line
* If a number is provided, the label will be rotated by that angle, in radians
*/
rotated?: boolean|number;
font?: FontConfig;
/**
* See strokeStyle: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle
Expand All @@ -46,6 +50,8 @@ export interface TicksConfig {
style?: string;
font?: FontConfig;
grid?: boolean;
/** rotate labels by some angle, in radians */
labelRotation?: number;
}
export declare type TicksValuesConfig = TicksConfig & ({
valueRange: [number, number];
Expand Down Expand Up @@ -77,8 +83,8 @@ export interface SingleAxisConfig {
keepOffsetContent?: boolean;
}
export interface AxesConfig {
x: boolean | Partial<SingleAxisConfig>;
y: boolean | Partial<SingleAxisConfig>;
x: boolean|(Partial<SingleAxisConfig>&{position?: "bottom"|"top"});
y: boolean|(Partial<SingleAxisConfig>&{position?: "left"|"right"});
font?: FontConfig;
/**
* See strokeStyle: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle
Expand Down
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ export declare class Canvas2dZoom extends HTMLElement {
resetZoomPan(): void;
/**
* Delete all content written previously
* @param options->keepCustomDrawn: if set to true, then everything added via drawCustom(), incl. axes drawn via LineUtils, will be retained
*/
clear(): void;
clear(options?: {keepCustomDrawn?: boolean}): void;
/**
* Zoom the canvas
* @param scale a number > 0; to zoom in, provide a value > 1 (2 is a good example), to zoom out provide a value < 1 (e.g. 0.5)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "canvas2d-zoom",
"version": "0.1.4",
"version": "0.1.6",
"description": "A webcomponent that adds zoom and pan behaviour to a canvas element",
"type": "module",
"exports": {
Expand Down
6 changes: 4 additions & 2 deletions src/ContextProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class ContextProxy implements ProxyHandler<CanvasRenderingContext2D> {
}

resetZoom() {
if (this.#pipe.length === 0)
if (this.#pipe.length === 0) // FIXME problematic, since this may be relevant to custom drawn elements
return;
const ctx: CanvasRenderingContext2D = this.#pipe[0].target;
ctx.restore();
Expand All @@ -88,14 +88,16 @@ export class ContextProxy implements ProxyHandler<CanvasRenderingContext2D> {
ctx.restore();
}

clear() {
clear(options?: {dispatch?: boolean}) {
if (this.#pipe.length === 0)
return;
const ctx: CanvasRenderingContext2D = this.#pipe[0].target;
this.#pipe.splice(0, this.#pipe.length);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(-this.#clearXBorder, -this.#clearYBorder, ctx.canvas.width + this.#clearXBorder, ctx.canvas.height + this.#clearYBorder);
this._eventDispatcher.dispatchEvent(new Event("clear"));
if (options?.dispatch)
this._dispatch(ctx, ctx.getTransform(), ctx.getTransform(), true, true); // ?
}

private _dispatch(ctx: CanvasRenderingContext2D, oldTransform: DOMMatrix, newTransform: DOMMatrix, zoom: boolean, pan: boolean) {
Expand Down
73 changes: 51 additions & 22 deletions src/LineUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ class AxesMgmt {

readonly #x: boolean;
readonly #y: boolean;
readonly #xConfig: SingleAxisConfig;
readonly #yConfig: SingleAxisConfig;
readonly #xConfig: SingleAxisConfig&{position?: "bottom"|"top"};
readonly #yConfig: SingleAxisConfig&{position?: "left"|"right"};

constructor(_config: Partial<AxesConfig>) {
this.#x = _config?.x !== false;
Expand Down Expand Up @@ -116,39 +116,50 @@ class AxesMgmt {
let yOffset: number = this.#xConfig.offsetBoundary;
if (yOffset < 0)
yOffset = Math.min(Math.round(height/10), 50);
const isXTop: boolean = this.#x && this.#xConfig.position === "top";
const isYRight: boolean = this.#y && this.#yConfig.position === "right";
if (this.#x) {
const c: SingleAxisConfig = this.#xConfig;
if (!c.keepOffsetContent) {
// draw a white rectangle
ctx.fillStyle = "white";
ctx.fillRect(0, height-yOffset, width, height);
if (isXTop)
ctx.fillRect(0, 0, width, yOffset);
else
ctx.fillRect(0, height-yOffset, width, height);
}
const yPosition: number = isXTop ? yOffset : height - yOffset;
// @ts-ignore
LineUtils._drawLine(ctx, c.offsetDrawn ? 0 : xOffset, height - yOffset, width, height - yOffset, c.lineConfig);
LineUtils._drawLine(ctx, isYRight || c.offsetDrawn ? 0 : xOffset, yPosition, isYRight && !c.offsetDrawn ? width - xOffset : width, yPosition, c.lineConfig);
// @ts-ignore
if (c.ticks && (c.ticks.values || c.ticks.valueRange)) {
const config: TicksConfig = c.ticks as TicksConfig;
const tickEndX: number = c.lineConfig?.arrows?.end ? width - xOffset : width;
const ticks: Array<Tick> = AxesMgmt._getTickPositions(xOffset, height - yOffset, tickEndX, height - yOffset, config, state.newTransformation);
const tickEndX: number = isYRight || c.lineConfig?.arrows?.end ? width - xOffset : width;
const tickPosition: LabelPosition = isXTop ? LabelPosition.LEFT : LabelPosition.RIGHT;
const tickOffset: number = isXTop ? -config.length : config.length;
const ticks: Array<Tick> = AxesMgmt._getTickPositions(isYRight ? 0 : xOffset, yPosition, tickEndX, yPosition, config, state.newTransformation);
const gridExtensionY: number = isXTop ? height : 0;
for (const tick of ticks) {
const lineConfig: Partial<LineConfig> = {};
if (tick.label) {
lineConfig.label = { // TODO set stroke color, width etc
text: tick.label,
position: LabelPosition.LEFT
position: tickPosition
};
if (config.font)
lineConfig.label.font = config.font;
if (config.style) {
lineConfig.label.style = config.style;
lineConfig.style = config.style;
}
if (config.labelRotation)
lineConfig.label.rotated = config.labelRotation;
}
// @ts-ignore
LineUtils._drawLine(ctx, tick.x, tick.y + config.length, tick.x, tick.y, lineConfig);
LineUtils._drawLine(ctx, tick.x, tick.y + tickOffset, tick.x, tick.y, lineConfig);
if (config.grid) {
// @ts-ignore
LineUtils._drawLine(ctx, tick.x, tick.y, tick.x, 0, AxesMgmt._GRID_CONFIG);
LineUtils._drawLine(ctx, tick.x, tick.y, tick.x, gridExtensionY, AxesMgmt._GRID_CONFIG);
}
}
}
Expand All @@ -158,34 +169,43 @@ class AxesMgmt {
if (!c.keepOffsetContent) {
// draw a white rectangle
ctx.fillStyle = "white";
ctx.fillRect(0, 0, xOffset, height);
if (isYRight)
ctx.fillRect(width-xOffset, 0, width, height);
else
ctx.fillRect(0, 0, xOffset, height);
}
const xPosition: number = isYRight ? width - xOffset : xOffset;
// @ts-ignore
LineUtils._drawLine(ctx, xOffset, c.offsetDrawn ? height : height - yOffset, xOffset, 0, c.lineConfig);
LineUtils._drawLine(ctx, xPosition, isXTop || c.offsetDrawn ? height : height - yOffset, xPosition, isXTop && !c.offsetDrawn ? yOffset : 0, c.lineConfig);
// @ts-ignore
if (c.ticks && (c.ticks.values || c.ticks.valueRange)) {
const config: TicksConfig = c.ticks as TicksConfig;
const tickEndY: number = c.lineConfig?.arrows?.end ? yOffset : 0;
const ticks: Array<Tick> = AxesMgmt._getTickPositions(xOffset, height - yOffset, xOffset, tickEndY, config, state.newTransformation);
const tickEndY: number = isXTop || c.lineConfig?.arrows?.end ? yOffset : 0;
const ticks: Array<Tick> = AxesMgmt._getTickPositions(xPosition, isXTop ? height : height - yOffset, xPosition, tickEndY, config, state.newTransformation);
const tickPosition: LabelPosition = isYRight ? LabelPosition.RIGHT : LabelPosition.LEFT;
const tickOffset: number = isYRight ? config.length : -config.length;
const gridExtensionX: number = isYRight ? 0 : width;
for (const tick of ticks) {
const lineConfig: Partial<LineConfig> = {};
if (tick.label) {
lineConfig.label = { // TODO set stroke color, width etc
text: tick.label,
position: LabelPosition.LEFT
position: tickPosition
};
if (config.font)
lineConfig.label.font = config.font;
if (config.style) {
lineConfig.label.style = config.style;
lineConfig.style = config.style;
}
if (config.labelRotation)
lineConfig.label.rotated = config.labelRotation;
}
// @ts-ignore
LineUtils._drawLine(ctx, tick.x-config.length, tick.y, tick.x, tick.y, lineConfig);
LineUtils._drawLine(ctx, tick.x + tickOffset, tick.y, tick.x, tick.y, lineConfig);
if (config.grid) {
// @ts-ignore
LineUtils._drawLine(ctx, tick.x, tick.y, width, tick.y, AxesMgmt._GRID_CONFIG);
LineUtils._drawLine(ctx, tick.x, tick.y, gridExtensionX, tick.y, AxesMgmt._GRID_CONFIG);
}
}
}
Expand Down Expand Up @@ -300,7 +320,11 @@ export interface LabelConfig {
size?: number; // TODO specify
position?: LabelPosition;
lineOffsetFactor?: number;
rotated?: boolean; // default: false
/**
* Default: false. If set to true, the label will be rotated with the same angle as the axis against a horizontal line
* If a number is provided, the label will be rotated by that angle, in radians
*/
rotated?: boolean|number;
font?: FontConfig;
/**
* See strokeStyle: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle
Expand Down Expand Up @@ -332,6 +356,8 @@ export interface TicksConfig {
font?: FontConfig;
// TODO option to define grid style
grid?: boolean;
/** rotate labels by some angle, in radians */
labelRotation?: number;
}

// FIXME even for string values a zooming effect may be desirable!
Expand Down Expand Up @@ -373,8 +399,8 @@ export interface SingleAxisConfig {
}

export interface AxesConfig {
x: boolean|Partial<SingleAxisConfig>;
y: boolean|Partial<SingleAxisConfig>;
x: boolean|(Partial<SingleAxisConfig>&{position?: "bottom"|"top"});
y: boolean|(Partial<SingleAxisConfig>&{position?: "left"|"right"});
font?: FontConfig;
/**
* See strokeStyle: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle
Expand Down Expand Up @@ -489,11 +515,14 @@ export class LineUtils {
ctx.fillStyle = config.label.style || "black";
if (config.label.font)
ctx.font = LineUtils._toFontString(config.label.font);
const position: [number, number] = LineUtils._getLabelPosition(length, angle, config.label, ctx);
const rotated: boolean = config.label.rotated;
const rotated: boolean|number = config.label.rotated;
const angle2 = isFinite(rotated as number) ? angle - (rotated as number) : angle;
const position: [number, number] = LineUtils._getLabelPosition(length, angle2, config.label, ctx);
ctx.translate(position[0], position[1]);
if (!rotated && angle !== 0)
if ((rotated === false || rotated === undefined) && angle !== 0)
ctx.rotate(-angle);
else if (isFinite(rotated as number))
ctx.rotate(-angle2);
ctx.fillText(config.label.text, 0, 0);
}
ctx.restore();
Expand Down
11 changes: 7 additions & 4 deletions src/canvas2d-zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,14 @@ export class Canvas2dZoom extends HTMLElement {

/**
* Delete all content written previously
* @param options->keepCustomDrawn: if set to true, then everything added via drawCustom(), incl. axes drawn via LineUtils, will be retained
*/
clear() {
Array.from(this.#zoomListeners.values()).forEach(listener => this.removeEventListener("zoom", listener));
this.#zoomListeners.clear();
this.#proxy.clear();
clear(options?: {keepCustomDrawn?: boolean}) {
if (!options?.keepCustomDrawn) {
Array.from(this.#zoomListeners.values()).forEach(listener => this.removeEventListener("zoom", listener));
this.#zoomListeners.clear();
}
this.#proxy.clear({dispatch: options.keepCustomDrawn}); // ensure redrawing of custom elements
}

/**
Expand Down

0 comments on commit 7c5e800

Please sign in to comment.