All examples

This page contains an index of all runnable examples in the main documentation. Additional examples can be found on GitHub.

Part of the goal of this page is to easily find broken examples.

Example 1

From page: documents/Guides.Getting_started.html

import Editor from 'js-draw';
import 'js-draw/styles';

const editor = new Editor(document.body);
editor.addToolbar();

Example 2

From page: documents/Guides.Getting_started.html

import Editor from 'js-draw';
import 'js-draw/bundledStyles';

const editor = new Editor(document.body);
editor.addToolbar();

Example 3

From page: documents/Guides.Getting_started.html

<!--
	Replace 1.27.1 with the latest version of js-draw, which should be 1.28.0.
-->
<script src="https://cdn.jsdelivr.net/npm/js-draw@1.27.1/dist/bundle.js"></script>
<script>
  const editor = new jsdraw.Editor(document.body);
  editor.addToolbar();
</script>

Example 4

From page: documents/Guides.Writing_a_theme.html

/* A yellowish theme! */

:root .imageEditorContainer {
	/* Try changing the below values and clicking run again! */
    /* Unselected buttons and dialog text. */
	--background-color-1: #ffff77;
	--foreground-color-1: black;

	/* Some menu/toolbar backgrounds. */
	--background-color-2: #ffff99;
	--foreground-color-2: #111;

	/* menu/toolbar backgrounds. */
	--background-color-3: #ffffaa;
	--foreground-color-3: #121100;

	/* Used for selected buttons. */
	--selection-background-color: #9f7;
	--selection-foreground-color: #00f;

	/* Used for dialog backgrounds */
	--background-color-transparent: rgba(0, 0, 100, 0.5);

	/* Used for shadows */
	--shadow-color: rgba(0, 0, 0, 0.5);

	/* Color used for some button/input foregrounds */
	--primary-action-foreground-color: #f00;

	/* Use light mode for controls. */
	color-scheme: light;
}

---ts---
import { Editor, makeEdgeToolbar, makeDropdownToolbar } from 'js-draw';
import 'js-draw/styles';
import { MaterialIconProvider } from '@js-draw/material-icons';



const makeToolbar = (newToolbar: boolean, editor: Editor) => {
	const toolbar = newToolbar ? makeEdgeToolbar(editor) : makeDropdownToolbar(editor);
	toolbar.addDefaults();

	toolbar.addExitButton(() => {
		alert('Not implemented for this editor!');
	});

	toolbar.addSaveButton(() => {
		const saveData = editor.toSVG().outerHTML;

		// Do something with saveData
		alert('Not implemented for this editor!');
	});

	return toolbar;
};

const makeEditor = async () => {
	const editor = new Editor(document.body, {
		iconProvider: new MaterialIconProvider(),
    	wheelEventsEnabled: 'only-if-focused',
	});

	// Template generated with https://js-draw.web.app/
	await editor.loadFromSVG(`
		<svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
			<g class="js-draw-image-background js-draw-image-background-grid js-draw-image-background-grid-25">
				<path d="M500,500L500,0L0,0L0,500L500,500" fill="#f1ffa4"></path>
				<path d="M0,0L500,0M0,25L500,25M0,50L500,50M0,75L500,75M0,100L500,100M0,125L500,125M0,150L500,150M0,175L500,175M0,200L500,200M0,225L500,225M0,250L500,250M0,275L500,275M0,300L500,300M0,325L500,325M0,350L500,350M0,375L500,375M0,400L500,400M0,425L500,425M0,450L500,450M0,475L500,475M0,500L500,500M0,0L0,500M25,0L25,500M50,0L50,500M75,0L75,500M100,0L100,500M125,0L125,500M150,0L150,500M175,0L175,500M200,0L200,500M225,0L225,500M250,0L250,500M275,0L275,500M300,0L300,500M325,0L325,500M350,0L350,500M375,0L375,500M400,0L400,500M425,0L425,500M450,0L450,500M475,0L475,500M500,0L500,500" fill="none" stroke="#0e005b33" stroke-width=".7"></path>
			</g>
		</svg>
	`);

	let isNewToolbar = true;
	let toolbar = makeToolbar(isNewToolbar, editor);

	const toolbarSelector = document.createElement('button');
	toolbarSelector.innerText = 'Change toolbar type';
	document.body.appendChild(toolbarSelector);

	toolbarSelector.onclick = () => {
		isNewToolbar = !isNewToolbar;
		toolbar.remove();

		toolbar = makeToolbar(isNewToolbar, editor);
	};
};

makeEditor();

Example 5

From page: documents/Guides.Components.Adding_and_modifying_components.html

import {
	Editor, EditorImage, Stroke, Path, Color4,
} from 'js-draw';

// 1.
const editor = new Editor(document.body);
editor.addToolbar();

// Create path data that we'll use to make the stroke.
const path = Path.fromString('m0,0 l0,40 l40,4 l0,-48 z');

// 2.
const stroke = Stroke.fromFilled(
	path, Color4.red,
);

// 3.
const command = editor.image.addComponent(stroke);

// 4.
editor.dispatch(command);

Example 6

From page: documents/Guides.Components.Adding_and_modifying_components.html

import {
	Editor, EditorImage, Stroke, Path, Color4, uniteCommands,
} from 'js-draw';

const editor = new Editor(document.body);
editor.addToolbar();

// 1.
const commands = [];
for (let x = 0; x < 100; x += 10) {
	for (let y = 0; y < 100; y += 10) {
		// 2. Try changing these!
		const strokeWidth = 3;
		const strokeColor = Color4.orange;

		// 3.
		const stroke = Stroke.fromStroked(
			// A path that starts at (i,i) then moves three units to the right
			`m${x},${y} l3,0`,
			{ width: strokeWidth, color: strokeColor },
		);
		const command = editor.image.addComponent(stroke);
		commands.push(command);
	}
}

// 4.
const compoundCommand = uniteCommands(commands);
editor.dispatch(compoundCommand);

Example 7

From page: documents/Guides.Components.Adding_and_modifying_components.html

---use-previous---
---visible---
// This example starts by running the code from the previous example --
// make sure the previous example compiles!
import { Rect2 } from 'js-draw';

// 1.
const components = editor.image.getComponentsIntersecting(
	new Rect2( // a 2D rectangle
		5, // x
		6, // y
		60, // width
		30, // height
	)
);

// 2.
const styleCommands = [];
for (const component of components) {
	// Only process Strokes -- there **are** other types of components.
	if (!(component instanceof Stroke)) continue;

	const command = component.updateStyle({ color: Color4.red });
	styleCommands.push(command);
}
// 3.
editor.dispatch(uniteCommands(styleCommands));

Example 8

From page: documents/Guides.Components.Adding_and_modifying_components.html

---use-previous---
---visible---
// This example starts by running the code from the previous example --
// make sure the previous example compiles!
import { Mat33, Vec2 } from 'js-draw';

const transformCommands = [];
for (const component of components) {
	const command = component.transformBy(
		Mat33.translation(Vec2.of(45, 0))
	);
	transformCommands.push(command);
}
editor.dispatch(uniteCommands(transformCommands));

Example 9

From page: documents/Guides.Components.Adding_and_modifying_components.html

---use-previous---
---visible---
// This example starts by running the code from the previous example --
// make sure the previous example compiles!
import { Erase } from 'js-draw';

// Deletes all components found in the previous steps
const eraseCommand = new Erase(components);
editor.dispatch(eraseCommand);

Example 10

From page: documents/Guides.Components.Custom_components.html

import { Editor } from 'js-draw';
const editor = new Editor(document.body);
editor.addToolbar();

Example 11

From page: documents/Guides.Components.Custom_components.html

---use-previous---
---visible---
import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
import { AbstractRenderer, AbstractComponent } from 'js-draw';

const componentId = 'example';
class ExampleComponent extends AbstractComponent {
	protected contentBBox: Rect2;

	public constructor() {
		super(componentId);

		// For now, create a 50x50 bounding box centered at (0,0).
		// We'll update this later:
		this.contentBBox = new Rect2(0, 0, 50, 50);
	}

	public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
		// Be sure that everything drawn between .startObject and .endObject is within contentBBox.
		// Content outside contentBBox may not be drawn in all cases.
		canvas.startObject(this.contentBBox);

		// _visibleRect is the part of the image that's currently visible. We can
		// ignore it for now.

		canvas.fillRect(this.contentBBox, Color4.red);

		// Ends the object and attaches any additional metadata attached by an image loader
		// (e.g. if this object was created by SVGLoader).
		canvas.endObject(this.getLoadSaveData());
	}

	// Must be implemented by all components, used for things like erasing and selection.
	public intersects(line: LineSegment2) {
		// TODO: For now, our component won't work correctly if the user tries to select it.
		// We'll implement this later.
		return false;
	}

	protected applyTransformation(transformation: Mat33): void {
		// TODO: Eventually, this should move the component. We'll implement this later.
	}

	protected createClone(): AbstractComponent {
		return new ExampleComponent();
	}

	public description(): string {
		// This should be a brief description of the component's content (for
		// accessibility tools)
		return 'a red box';
	}

	protected serializeToJSON() {
		return JSON.stringify({
			// Some data to save (for collaborative editing)
		});
	}
}

// data: The data serialized by serlailzeToJSON
AbstractComponent.registerComponent(componentId, data => {
	// TODO: Deserialize the component from [data]. This is used if collaborative
	// editing is enabled.
	return new ExampleComponent();
});

// Add the component
editor.dispatch(editor.image.addComponent(new ExampleComponent()));

Example 12

From page: documents/Guides.Components.Custom_components.html

import { Editor } from 'js-draw';
const editor = new Editor(document.body);
editor.addToolbar();
---visible---
import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
import { AbstractRenderer, AbstractComponent } from 'js-draw';

const componentId = 'example';
class ExampleComponent extends AbstractComponent {
	protected contentBBox: Rect2;

	// NEW: Stores the scale/rotation/position. "Transform" is short for "transformation".
	private transform: Mat33;
	// NEW: Stores the untransformed bounding box of the component. If the component hasn't
	// been moved/scaled yet, initialBBox should completely contain the component's content.
	private initialBBox: Rect2;

	public constructor(transform: Mat33) {
		super(componentId);

		this.transform = transform;
		this.initialBBox = new Rect2(0, 0, 50, 50);
		this.updateBoundingBox();
	}

	// NEW: Updates this.contentBBox. Should be called whenever this.transform changes.
	private updateBoundingBox() {
		this.contentBBox = this.initialBBox.transformedBoundingBox(
			this.transform,
		);
	}

	public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
		canvas.startObject(this.contentBBox);

		// Everything between .pushTransform and .popTransform will be scaled/rotated by this.transform.
		// Try removing the .pushTransform and .popTransform lines!
		canvas.pushTransform(this.transform);
		canvas.fillRect(this.initialBBox, Color4.red);
		canvas.popTransform();

		// After the call to .popTransform, this.transform is no longer transforming the canvas.
		// Try uncommenting the following line:
		// canvas.fillRect(this.initialBBox, Color4.orange);
		// What happens when the custom component is selected and moved?
		// What happens to the orange rectangle when the red rectangle is moved offscreen?

		canvas.endObject(this.getLoadSaveData());
	}

	public intersects(line: LineSegment2) {
		// Our component is currently just a rectangle. As such (for some values of this.transform),
		// we can use the Rect2.intersectsLineSegment method here:
		const intersectionCount = this.contentBBox.intersectsLineSegment(line).length;
		return intersectionCount > 0; // What happpens if you always return `true` here?
	}

	protected applyTransformation(transformUpdate: Mat33): void {
		// `.rightMul`, "right matrix multiplication" combines two transformations.
		// The transformation on the left is applied **after** the transformation on the right.
		// As such, `transformUpdate.rightMul(this.transform)` means that `this.transform`
		// will be applied **before** the `transformUpdate`.
		this.transform = transformUpdate.rightMul(this.transform);
		this.updateBoundingBox();
	}

	protected createClone(): AbstractComponent {
		const clone = new ExampleComponent(this.transform);
		return clone;
	}

	public description(): string {
		return 'a red box';
	}

	protected serializeToJSON() {
		return JSON.stringify({
			// TODO: Some data to save (for collaborative editing)
		});
	}
}

// data: The data serialized by serlailzeToJSON
AbstractComponent.registerComponent(componentId, data => {
	// TODO: Deserialize the component from [data]. This is used if collaborative
	// editing is enabled.
	return new ExampleComponent(Mat33.identity);
});

// Add the component
const initialTransform = Mat33.identity;
editor.dispatch(editor.image.addComponent(new ExampleComponent(initialTransform)));

Example 13

From page: documents/Guides.Components.Custom_components.html

import { Editor, SVGLoaderPlugin, Stroke } from 'js-draw';
import { Color4 } from '@js-draw/math';

let nextX = 0;
const testPlugin: SVGLoaderPlugin = {
	async visit(node, loader) {
		if (node.tagName.toLowerCase() === 'text') {
			const testComponent = Stroke.fromFilled(
				`m${nextX},0 l50,0 l0,50 z`, Color4.red,
			);
			nextX += 100;
			loader.addComponent(testComponent);
			return true;
		}
		// Return false to do the default image loading
		return false;
	}
};

const editor = new Editor(document.body, {
	svg: {
		loaderPlugins: [ testPlugin ],
	}
});
editor.addToolbar();

// With the loader plugin, text objects are converted to red triangles.
editor.loadFromSVG(`
	<svg>
		<text>test</text>
		<text y="50">test 2</text>
		<text y="100">test 3</text>
	</svg>
`);

Example 14

From page: documents/Guides.Components.Custom_components.html

import { Editor } from 'js-draw';
import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
import { AbstractRenderer, AbstractComponent } from 'js-draw';

const componentId = 'example';
class ExampleComponent extends AbstractComponent {
	protected contentBBox: Rect2;

	private transform: Mat33;
	private initialBBox: Rect2;

	public constructor(transform: Mat33) {
		super(componentId);

		this.transform = transform;
		this.initialBBox = new Rect2(0, 0, 50, 50);
		this.updateBoundingBox();
	}

	private updateBoundingBox() {
		this.contentBBox = this.initialBBox.transformedBoundingBox(
			this.transform,
		);
	}

	public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
		canvas.startObject(this.contentBBox);

		canvas.pushTransform(this.transform);
		canvas.fillRect(this.initialBBox, Color4.red);
		canvas.popTransform();

		const containerClassNames = ['comp--example-component'];
		canvas.endObject(this.getLoadSaveData(), containerClassNames);
	}

	public intersects(line: LineSegment2) {
		// Our component is currently just a rectangle. As such (for some values of this.transform),
		// we can use the Rect2.intersectsLineSegment method here:
		const intersectionCount = this.contentBBox.intersectsLineSegment(line).length;
		return intersectionCount > 0; // What happpens if you always return `true` here?
	}

	protected applyTransformation(transformUpdate: Mat33): void {
		// `.rightMul`, "right matrix multiplication" combines two transformations.
		// The transformation on the left is applied **after** the transformation on the right.
		// As such, `transformUpdate.rightMul(this.transform)` means that `this.transform`
		// will be applied **before** the `transformUpdate`.
		this.transform = transformUpdate.rightMul(this.transform);
		this.updateBoundingBox();
	}

	protected createClone(): AbstractComponent {
		const clone = new ExampleComponent(this.transform);
		return clone;
	}

	public description(): string {
		return 'a red box';
	}

	protected serializeToJSON() {
		return JSON.stringify({
			// TODO: Some data to save (for collaborative editing)
		});
	}
}

AbstractComponent.registerComponent(componentId, data => {
	// TODO:
	return new ExampleComponent(Mat33.identity);
});

const plugin: SVGLoaderPlugin = {
	async visit(node, loader) {
		if (node.classList.contains('comp--example-component')) {
			// TODO: Set the transformation matrix correctly -- get this information
			// from the `node`. This isn't too important for copy/paste support.
			const customComponent = new ExampleComponent(Mat33.identity);
			loader.addComponent(customComponent);
			return true;
		}
		// Return false to do the default image loading
		return false;
	},
};

const editor = new Editor(document.body, {
	svg: {
		loaderPlugins: [ plugin ],
	},
});
editor.addToolbar();

// Add the component
const initialTransform = Mat33.identity;
editor.dispatch(editor.image.addComponent(new ExampleComponent(initialTransform)));

Example 15

From page: documents/Guides.Components.Custom_components.html

import { Editor, invertCommand, SerializableCommand, EditorEventType } from 'js-draw';

const editor1 = new Editor(document.body);
// Store the toolbar in a variable -- we'll use it later
const toolbar = editor1.addToolbar();

const editor2 = new Editor(document.body);
editor2.addToolbar();

const applySerializedCommand = (serializedCommand: any, editor: Editor) => {
	const command = SerializableCommand.deserialize(serializedCommand, editor);
	command.apply(editor);
};

const applyCommandsToOtherEditor = (sourceEditor: Editor, otherEditor: Editor) => {
	sourceEditor.notifier.on(EditorEventType.CommandDone, (evt) => {
		// Type assertion.
		if (evt.kind !== EditorEventType.CommandDone) {
			throw new Error('Incorrect event type');
		}

		if (evt.command instanceof SerializableCommand) {
			const serializedCommand = evt.command.serialize();
			applySerializedCommand(serializedCommand, otherEditor);
		} else {
			console.log('Nonserializable command');
		}
	});
	sourceEditor.notifier.on(EditorEventType.CommandUndone, (evt) => {
		// Type assertion.
		if (evt.kind !== EditorEventType.CommandUndone) {
			throw new Error('Incorrect event type');
		}

		if (evt.command instanceof SerializableCommand) {
			const serializedCommand = invertCommand(evt.command).serialize();
			applySerializedCommand(serializedCommand, otherEditor);
		} else {
			console.log('Nonserializable command');
		}
	});
};

applyCommandsToOtherEditor(editor1, editor2);
applyCommandsToOtherEditor(editor2, editor1);

Example 16

From page: documents/Guides.Components.Custom_components.html

---use-previous---
import { Editor } from 'js-draw';
import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
import { AbstractRenderer, AbstractComponent } from 'js-draw';

const componentId = 'example';
class ExampleComponent extends AbstractComponent {
	protected contentBBox: Rect2;

	private transform: Mat33;
	private initialBBox: Rect2;

	public constructor(transform: Mat33) {
		super(componentId);

		this.transform = transform;
		this.initialBBox = new Rect2(0, 0, 50, 50);
		this.updateBoundingBox();
	}

	private updateBoundingBox() {
		this.contentBBox = this.initialBBox.transformedBoundingBox(
			this.transform,
		);
	}

	public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
		canvas.startObject(this.contentBBox);

		canvas.pushTransform(this.transform);
		canvas.fillRect(this.initialBBox, Color4.red);
		canvas.popTransform();

		const containerClassNames = ['comp--example-component'];
		canvas.endObject(this.getLoadSaveData(), containerClassNames);
	}

	public intersects(line: LineSegment2) {
		// Our component is currently just a rectangle. As such (for some values of this.transform),
		// we can use the Rect2.intersectsLineSegment method here:
		const intersectionCount = this.contentBBox.intersectsLineSegment(line).length;
		return intersectionCount > 0; // What happpens if you always return `true` here?
	}

	protected applyTransformation(transformUpdate: Mat33): void {
		// `.rightMul`, "right matrix multiplication" combines two transformations.
		// The transformation on the left is applied **after** the transformation on the right.
		// As such, `transformUpdate.rightMul(this.transform)` means that `this.transform`
		// will be applied **before** the `transformUpdate`.
		this.transform = transformUpdate.rightMul(this.transform);
		this.updateBoundingBox();
	}

	protected createClone(): AbstractComponent {
		const clone = new ExampleComponent(this.transform);
		return clone;
	}

	public description(): string {
		return 'a red box';
	}

---visible---
	// ...other component logic...

	protected serializeToJSON() {
		return JSON.stringify({
			// NEW: Save the transform matrix:
			transform: this.transform.toArray(),
		});
	}
}

AbstractComponent.registerComponent(componentId, data => {
	const transformArray = JSON.parse(data).transform;

	// NEW: Validation
	if (!Array.isArray(transformArray)) {
		throw new Error('data.transform must be an array');
	}
	for (const entry of transformArray) {
		if (!isFinite(entry)) {
			throw new Error(`Non-finite entry in transform: ${entry}`);
		}
	}

	// NEW: Create and return the component from the data
	const transform = new Mat33(...transformArray);
	return new ExampleComponent(transform);
});

// Make a button that adds the component
function makeAddIcon() {
	const container = document.createElement('div');
	container.textContent = '+';
	return container;
}

toolbar.addActionButton({
	icon: makeAddIcon(),
	label: 'Add test component',
}, () => {
	const initialTransform = Mat33.identity;
	const component = new ExampleComponent(initialTransform);

	// The addAndCenterComponents method automatically selects,
	// centers, and adds the provided components to the editor.
	//
	// We could also add the component using
	// editor.dispatch(editor.image.addComponent(component));
	editor1.addAndCenterComponents([
		component
	]);
});

Example 17

From page: documents/Guides.Components.Custom_components.html

import { Editor, CanvasRenderer, SVGRenderer } from 'js-draw';
import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
import { AbstractRenderer, AbstractComponent } from 'js-draw';

const componentId = 'example';
class ExampleComponent extends AbstractComponent {
	protected contentBBox: Rect2;

	private transform: Mat33;
	private initialBBox: Rect2;

	public constructor(transform: Mat33) {
		super(componentId);

		this.transform = transform;
		this.initialBBox = new Rect2(0, 0, 50, 50);
		this.updateBoundingBox();
	}

	private updateBoundingBox() {
		this.contentBBox = this.initialBBox.transformedBoundingBox(
			this.transform,
		);
	}

	public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
		canvas.startObject(this.contentBBox);

		canvas.pushTransform(this.transform);
		if (canvas instanceof CanvasRenderer) {
			canvas.drawWithRawRenderingContext(ctx => {
				ctx.strokeStyle = 'green';

				// Draw a large number of rectangles
				const rectSize = 20;
				const maximumX = this.initialBBox.width - rectSize;
				for (let x = 0; x < maximumX; x += 2) {
					ctx.strokeRect(x, 0, rectSize, rectSize);
				}
			});
		} else if (canvas instanceof SVGRenderer) {
			canvas.drawWithSVGParent(parent => {
				// Draw some text. Note that this can also
				// be done with canvas.drawText
				const text = document.createElementNS(
					'http://www.w3.org/2000/svg', 'text',
				);

				text.textContent = 'Text in an SVG element!';
				text.setAttribute('x', '50');
				text.setAttribute('y', '25');
				text.style.fill = 'red';

				parent.appendChild(text);
			});
		} else {
			// Fallback for other renderers
			canvas.fillRect(this.initialBBox, Color4.red);
		}
		canvas.popTransform();

		const containerClassNames = ['comp--example-component'];
		canvas.endObject(this.getLoadSaveData(), containerClassNames);
	}

	public intersects(line: LineSegment2) {
		return false; // TODO (see above sections for implementation)
	}

	protected applyTransformation(transformUpdate: Mat33): void {
		this.transform = transformUpdate.rightMul(this.transform);
		this.updateBoundingBox();
	}

	protected createClone(): AbstractComponent {
		const clone = new ExampleComponent(this.transform);
		return clone;
	}

	public description(): string {
		return 'a red box'; // TODO (see examples above)
	}

	protected serializeToJSON() {
		return JSON.stringify({
			// TODO: Some data to save (for collaborative editing)
		});
	}
}

AbstractComponent.registerComponent(componentId, data => {
	// TODO: See above examples for how to implement this
	// Needed for collaborative editing
	throw new Error('Not implemented');
});

const editor = new Editor(document.body);
editor.addToolbar();

// Add the component
const initialTransform = Mat33.identity;
editor.dispatch(editor.image.addComponent(new ExampleComponent(initialTransform)));

// Preview the SVG output
document.body.appendChild(editor.toSVG());

Example 18

From page: documents/Guides.Updating_the_viewport.html

import { Editor, Viewport, Mat33 } from 'js-draw';
const editor = new Editor(document.body); // 1
editor.addToolbar();

const command = Viewport.transformBy(Mat33.scaling2D(1/4)); // 2
editor.dispatch(command); // 3

Example 19

From page: documents/Guides.Updating_the_viewport.html

import { Editor, Viewport, Mat33 } from 'js-draw';
const editor = new Editor(document.body); // 1
editor.addToolbar();

---visible---
const command = Viewport.transformBy(Mat33.scaling2D(1/4)); // 2
editor.dispatch(command, false); // false: Don't add to history

// Alternatively,
// command.apply(editor);

Example 20

From page: documents/Guides.Updating_the_viewport.html

import { Editor } from 'js-draw';

// Create an editor with a toolbar:
const editor = new Editor(document.body);
editor.addToolbar();

Example 21

From page: documents/Guides.Updating_the_viewport.html

---use-previous---
---visible---
import { Color4, BackgroundComponentBackgroundType } from 'js-draw';

editor.dispatch(
  editor.setBackgroundStyle({
    color: Color4.orange,
    type: BackgroundComponentBackgroundType.Grid,

	// Make the background autoresize so that it's always
	// visible:
	autoresize: true,
  }),
);

Example 22

From page: documents/Guides.Updating_the_viewport.html

---use-previous---
---visible---
import { Viewport } from 'js-draw';
import { Mat33, Vec2 } from '@js-draw/math';

// When moveLeftUpdate is applied to the viewport, it moves the
// canvas to the left by 1 unit. This is the same as moving the viewport
// to the right by one unit.
const moveLeftUpdate = Mat33.translation(Vec2.of(-1, 0));

function update() {
	const moveLeftCommand = Viewport.transformBy(moveLeftUpdate);
	moveLeftCommand.apply(editor);

	requestAnimationFrame(update);
}
update();

Example 23

From page: documents/Guides.Updating_the_viewport.html

import { Editor } from 'js-draw';
import { Color4, BackgroundComponentBackgroundType } from 'js-draw';

const editor = new Editor(document.body);
editor.addToolbar();

editor.dispatch(
  editor.setBackgroundStyle({
    color: Color4.orange,
    type: BackgroundComponentBackgroundType.Grid,
	autoresize: true,
  }),
);

---visible---
import { Viewport } from 'js-draw';
import { Mat33, Vec2 } from '@js-draw/math';

let lastTime = performance.now();
function update() {
	// Get how long many milliseconds have elapsed since the last update.
	const nowTime = performance.now();
	const millisecondsElapsed = nowTime - lastTime;
	const seconds = millisecondsElapsed / 1000;
	lastTime = nowTime;

	const moveLeftRate = -10; // units/second
	const moveLeftAmount = Vec2.of(moveLeftRate * seconds, 0);
	const moveLeftUpdate = Mat33.translation(moveLeftAmount);
	const moveLeftCommand = Viewport.transformBy(
		moveLeftUpdate
	);
	moveLeftCommand.apply(editor);

	requestAnimationFrame(update);
}
update();

Example 24

From page: documents/Guides.Customizing_existing_tools.html

import {
	Editor, PenTool, Color4
} from 'js-draw';
import 'js-draw/styles';
const editor = new Editor(document.body, {
	wheelEventsEnabled: 'only-if-focused',
});

// The toolbar can be added either before or after changing the tool.
editor.addToolbar();

// Get all tools of type PenTool (we could also do this with an EraserTool).
const penTools = editor.toolController.getMatchingTools(PenTool);

// Get the second pen tool (somewhat fragile -- js-draw might change
// the default toolbar configuration in a future major release).
const secondPen = penTools[1];

secondPen.setThickness(200);
secondPen.setColor(Color4.red);

Example 25

From page: documents/Guides.Customizing_existing_tools.html

import {
	Editor, PenTool, Color4, makeOutlinedCircleBuilder,
} from 'js-draw';
import 'js-draw/styles';
const editor = new Editor(document.body, {
	wheelEventsEnabled: 'only-if-focused',
});

const toolController = editor.toolController;

const originalPenTools = toolController.getMatchingTools(PenTool);

// Add a new pen after the existing
const penStyle: PenStyle = {
	color: Color4.red,
	// Draw circles by default
	factory: makeOutlinedCircleBuilder,
	thickness: 4,
};

const newPen = new PenTool(editor, 'My custom pen', penStyle);

// Add after the first pre-existing pen tool
toolController.insertToolsAfter(originalPenTools[0], [ newPen ]);

// Remove the previous pen tools
toolController.removeAndDestroyTools(originalPenTools);

// Make the new pen a primary tool -- it disables other primary tools
// when the user first enables it (like the default eraser/text tool/pens)
toolController.addPrimaryTool(newPen);

// Must be done after changing the tools:
editor.addToolbar();

Example 26

From page: documents/Guides.Customizing_existing_tools.html

import { Editor, makePolylineBuilder, makeOutlinedCircleBuilder, ComponentBuilderFactory } from 'js-draw';

// A pen factory's job is to return a ComponentBuilder when starting a new stroke.
// Below, we randomly choose between a circle ComponentBuilder and a polyline pen ComponentBuilder.
//
// Parameters:
//   startPoint - the first point on the stroke.
//   viewport - information about the current rotation/scale of the drawing canvas.
const customPenFactory: ComponentBuilderFactory = (startPoint, viewport) => {
	// Randomly choose either a polyline or a circle for this stroke.
	if (Math.random() < 0.5) {
		return makePolylineBuilder(startPoint, viewport);
	} else {
		return makeOutlinedCircleBuilder(startPoint, viewport);
	}
};

const editor = new Editor(document.body, {
	pens: {
		// The polyline is already present by default --
		additionalPenTypes: [{
			name: 'Polyline pen',
			id: 'custom-polyline',
			factory: customPenFactory,

			// The pen doesn't create fixed shapes (e.g. squares, rectangles, etc)
			// and so should go under the "pens" section.
			isShapeBuilder: false,
		}],
	},
});
editor.addToolbar();

Example 27

From page: documents/Guides.Customizing_existing_tools.html

---use-previous---
---visible---
import { PenTool } from 'js-draw';

const firstPen = editor.toolController.getMatchingTools(PenTool)[0];
firstPen.setStrokeFactory(customPenFactory);

Example 28

From page: documents/Guides.Customizing_existing_tools.html

import {
	pathToRenderable, Path, Stroke, ComponentBuilderFactory, Point2, Vec2, Rect2, Color4, Viewport, StrokeDataPoint, RenderingStyle, PathCommandType, ComponentBuilder, AbstractRenderer
} from 'js-draw';


///
/// The custom ComponentBuilder
///
/// This class handles conversion between input data (for example, as generated
/// by a mouse) and AbstractComponents that will be added to the drawing.
///

class CustomBuilder implements ComponentBuilder {
	private path: Path;
	private renderingStyle: RenderingStyle;
	private lastPoint: Point2;

	public constructor(
		startPoint: StrokeDataPoint,

		// We'll use sizeOfScreenPixelOnCanvas later, to round points
		// based on the current zoom level.
		private sizeOfScreenPixelOnCanvas: number
	) {
		// Round points based on the current zoom to prevent the saved SVG
		// from having large decimals.
		const startPosition = this.roundPoint(startPoint.pos);

		// Initially, just a point:
		this.path = new Path(startPosition, []);

		this.renderingStyle = {
			// No fill
			fill: Color4.transparent,

			stroke: {
				color: startPoint.color,

				// For now, the custom pen has a constant width based on the first
				// point.
				width: startPoint.width,
			},
		};

		this.lastPoint = startPosition;
	}

	// Returns the bounding box of the stroke drawn so far. This box should contain
	// all points in the stroke.
	public getBBox(): Rect2 {
		return this.path.bbox;
	}

	// Called to build the final version of the stroke.
	public build(): Stroke {
		return new Stroke([ pathToRenderable(this.path, this.renderingStyle) ]);
	}

	// Called while building the stroke. This is separate from .build() to
	// allow for greater efficiency (.build creates the final version, .preview
	// can be a fast preview).
	public preview(renderer: AbstractRenderer) {
		// For simplicity, use the same final shape as the preview.
		const stroke = this.build();
		stroke.render(renderer);
	}

	private roundPoint(point: Point2): Point2 {
		// Because js-draw supports a very large zoom range, we round differently
		// at different zoom levels. sizeOfScreenPixelOnCanvas is based on the current zoom level.
		return Viewport.roundPoint(point, this.sizeOfScreenPixelOnCanvas);
	}

	// .addPoint is called when a new point of input data has been received.
	// newPoint contains color, pressure, and position information.
	public addPoint(newPoint: StrokeDataPoint) {
		// Create a new point based on the input data, plus some randomness!
		const size = newPoint.width * 4;
		const newPos = newPoint.pos.plus(
			Vec2.of(Math.random() * size - size/2, Math.random() * size - size/2)
		);

		// Round the point to prevent long decimal values when saving to SVG.
		const roundedPoint = this.roundPoint(newPos);

		this.path = new Path(this.path.startPoint, [
			...this.path.parts,
			{
				kind: PathCommandType.LineTo,
				point: roundedPoint,
			},
		]);
	}
}

///
/// The custom ComponentBuilderFactory
///


// A ComponentBuilderFactory is responsible for creating instances of a
// ComponentBuilder. It's what we'll provide to js-draw.
export const makeCustomBuilder: ComponentBuilderFactory =
	(initialPoint: StrokeDataPoint, viewport: Viewport) => {
		const sizeOfScreenPixelOnCanvas = viewport.getSizeOfPixelOnCanvas();
		return new CustomBuilder(initialPoint, sizeOfScreenPixelOnCanvas);
	};


///
/// The editor
///

import { Editor } from 'js-draw';

const editor = new Editor(document.body, {
	pens: {
		additionalPenTypes: [{
			name: 'Wavy pen',
			id: 'wavy-lines',
			factory: makeCustomBuilder,

			// Put under the "pens" section.
			isShapeBuilder: false,
		}],
	},
});

editor.addToolbar();

///
/// Select our custom pen by default.
///

import { PenTool } from 'js-draw';

const firstPen = editor.toolController.getMatchingTools(PenTool)[0];
firstPen.setStrokeFactory(makeCustomBuilder);

Example 29

From page: documents/Guides.Positioning_an_element_above_the_editor.html

import { Editor } from 'js-draw';

// Adds the editor to document.body:
const editor = new Editor(document.body); // 1
editor.addToolbar();

Example 30

From page: documents/Guides.Positioning_an_element_above_the_editor.html

---use-previous---
---visible---
const button = document.createElement('button');
button.textContent = 'Example!';
button.style.position = 'absolute';

Example 31

From page: documents/Guides.Positioning_an_element_above_the_editor.html

---use-previous---
---visible---
import { Mat33, Vec2 } from '@js-draw/math';

const positioning = Mat33.translation(Vec2.of(10, 20));
const anchor = editor.anchorElementToCanvas(button, positioning);

Example 32

From page: documents/Guides.Positioning_an_element_above_the_editor.html

---use-previous---
---visible---
button.onclick = () => {
	anchor.remove();
};

Example 33

From page: documents/Migrations.Migrating_to_version_1.html

:root .imageEditorContainer {
    /* Used for unselected buttons and dialog text. */
	--background-color-1: white;
	--foreground-color-1: black;

	/* Used for some menu/toolbar backgrounds. */
	--background-color-2: #f5f5f5;
	--foreground-color-2: #2c303a;

	/* Used for other menu/toolbar backgrounds. */
	--background-color-3: #e5e5e5;
	--foreground-color-3: #1c202a;

	/* Used for selected buttons. */
	--selection-background-color: #cbdaf1;
	--selection-foreground-color: #2c303a;

	/* Used for dialog backgrounds */
	--background-color-transparent: rgba(105, 100, 100, 0.5);

	/* Used for shadows */
	--shadow-color: rgba(0, 0, 0, 0.5);

	/* Color used for some button/input foregrounds */
	--primary-action-foreground-color: #15b;
}

---ts---
import { Editor, makeEdgeToolbar, makeDropdownToolbar } from 'js-draw';
import 'js-draw/styles';
import { MaterialIconProvider } from '@js-draw/material-icons';



const makeToolbar = (newToolbar: boolean, editor: Editor) => {
	const toolbar = newToolbar ? makeEdgeToolbar(editor) : makeDropdownToolbar(editor);
	toolbar.addDefaults();

	toolbar.addExitButton(() => {
		alert('Not implemented for this editor!');
	});

	toolbar.addSaveButton(() => {
		const saveData = editor.toSVG().outerHTML;

		// Do something with saveData
		alert('Not implemented for this editor!');
	});

	return toolbar;
};

const makeEditor = async () => {
	const editor = new Editor(document.body, {
		iconProvider: new MaterialIconProvider(),
    	wheelEventsEnabled: 'only-if-focused',
	});

	// Loads from SVG data
	await editor.loadFromSVG(`
		<svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
			<style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style>
			<path d="M500,500L500,0L0,0L0,500L500,500" fill="#e3e3e3" class="js-draw-image-background"></path>
			<text style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: sans-serif; font-size: 32px; fill: rgb(0, 0, 0);">Testing...</text>
		</svg>
	`);

	let isNewToolbar = true;
	let toolbar = makeToolbar(isNewToolbar, editor);



	const toolbarSelector = document.createElement('button');
	toolbarSelector.innerText = 'Change toolbar type';
	document.body.appendChild(toolbarSelector);

	toolbarSelector.onclick = () => {
		isNewToolbar = !isNewToolbar;
		toolbar.remove();

		toolbar = makeToolbar(isNewToolbar, editor);
	};
};

makeEditor();

Example 34

From page: documents/Migrations.Migrating_to_version_1.html

import {
	Editor, makeEdgeToolbar, makeDropdownToolbar, AbstractToolbar
} from 'js-draw';
import 'js-draw/styles';

// Also use the new icon pack:
import { MaterialIconProvider } from '@js-draw/material-icons';

let toolbar: AbstractToolbar|null = null
let isDropdownToolbar: boolean = true;

const makeToolbar = (editor: Editor) => {
	// Remove the old toolbar (if any).
	if (toolbar) {
		toolbar.remove();
	}

	// Create the new toolbar
	if (isDropdownToolbar) {
		toolbar = makeDropdownToolbar(editor);
	} else {
		toolbar = makeEdgeToolbar(editor);
	}

	// Add the default action buttons to the toolbar
	toolbar.addDefaults();

	// Add a toggle button
	toolbar.addActionButton({
		// An icon that looks similar to an arrow:
		icon: editor.icons.makeDropdownIcon(),
		label: 'Change toolbar type'
	}, () => {
		isDropdownToolbar = !isDropdownToolbar;
		makeToolbar(editor);
	});

	// Optional: Add save/exit buttons:
	// toolbar.addExitButton(() => { });
	// toolbar.addSaveButton(() => { });

	return toolbar;
};

// Creates the edior and adds it to the document
const makeEditor = () => {
	const editor = new Editor(document.body, {
		iconProvider: new MaterialIconProvider(),
    	wheelEventsEnabled: 'only-if-focused',
	});

	makeToolbar(editor);
};

makeEditor();

Example 35

From page: documents/Migrations.Migrating_to_version_1.html

import {
	Editor, PenTool, PenStyle, Color4,
	makeOutlinedCircleBuilder, makeFreehandLineBuilder
} from 'js-draw';
import 'js-draw/styles';

const editor = new Editor(document.body, {
    wheelEventsEnabled: 'only-if-focused',
});

const penStyle: PenStyle = {
	color: Color4.red,
	// Try changing this to makeFreehandLineBuilder
	factory: makeOutlinedCircleBuilder,
	thickness: 4,
};

editor.toolController.addPrimaryTool(
	new PenTool(editor, 'Some description here', penStyle),
);


// Add the toolbar **after** adding the new tool.
editor.addToolbar();

Example 36

From page: modules/js-draw.html

import { Editor, Vec3, Mat33, EditorSettings, ShortcutManager } from 'js-draw';

// Use the Material Icon pack.
import { MaterialIconProvider } from '@js-draw/material-icons';

// Apply js-draw CSS
import 'js-draw/styles';
// If your bundler doesn't support the above, try
// import 'js-draw/bundledStyles';

(async () => {
	// All settings are optional! Try commenting them out.
	const settings: EditorSettings = {
		// Use a non-default set of icons
		iconProvider: new MaterialIconProvider(),

		// Only capture mouse wheel events if the editor has focus. This is useful
		// when the editor is part of a larger, scrolling page.
		wheelEventsEnabled: 'only-if-focused',
	};

	// Create the editor!
	const editor = new Editor(document.body, settings);

	// Adds the defualt toolbar
	const toolbar = editor.addToolbar();

	// Increases the minimum height of the editor
	editor.getRootElement().style.height = '600px';

	// Loads from SVG data
	await editor.loadFromSVG(`
		<svg
			viewBox="0 0 500 500"
			width="500" height="500"
			version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"
		>
			<path
				d="M500,500L500,0L0,0L0,500L500,500"
				fill="#aaa"
				class="js-draw-image-background"
			/>
			<text
				style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: serif; font-size: 32px; fill: #111;"
			>Testing...</text>
		</svg>
	`);

	// Adding tags to a toolbar button allows different styles to be applied.
	// Also see addActionButton.
	toolbar.addSaveButton(() => {
		const saveData = editor.toSVG().outerHTML;

		// Do something with saveData
		alert('Saved data!\n\n' + saveData);
	});

	toolbar.addExitButton(() => {
		// Save/confirm exiting here?
		editor.remove();
	});
})();

Example 37

From page: modules/js-draw.Vec2.html

import { Vec2 } from '@js-draw/math';

const v = Vec2.of(1, 2);
console.log('a Vec2:', v);
console.log('x component:', v.x);
console.log('z component:', v.z);

Example 38

From page: functions/js-draw.Vec2.of.html

import { Vec2 } from '@js-draw/math';
const v = Vec2.of(3, 4); // x=3, y=4.

Example 39

From page: functions/js-draw.Vec2.ofXY.html

import { Vec2 } from '@js-draw/math';
const v1 = Vec2.ofXY({ x: 3, y: 4.5 });
const v2 = Vec2.ofXY({ x: -123.4, y: 1 });

Example 40

From page: functions/js-draw.Vec3.of.html

import { Vec3 } from '@js-draw/math';
const v1 = Vec3.of(1, 2, 3);
console.log(v1.plus(Vec3.of(0, 100, 0)));

Example 41

From page: classes/js-draw.AbstractToolbar.html

import { Editor } from 'js-draw';
const editor = new Editor(document.body);
const toolbar = editor.addToolbar();

function makeTrashIcon() {
  const container = document.createElement('div');
  container.textContent = '🗑️';
  return container;
}

toolbar.addActionButton({
  icon: makeTrashIcon(), // can be any Element not in the DOM
  label: 'Delete all',
}, () => {
  alert('to-do!');

Example 42

From page: classes/js-draw.AbstractToolbar.html

import { Editor, makeDropdownToolbar } from 'js-draw';

const editor = new Editor(document.body);
const toolbar = makeDropdownToolbar(editor);

toolbar.addDefaults();
toolbar.addSaveButton(() => alert('save clicked!'));

Example 43

From page: classes/js-draw.CanvasRenderer.html

import {Editor,CanvasRenderer} from 'js-draw';

// Create an editor and load initial data -- don't add to the body (hidden editor).
const editor = new Editor(document.createElement('div'));
await editor.loadFromSVG('<svg><path d="m0,0 l100,5 l-50,60 l30,20 z" fill="green"/></svg>');
---visible---
// Given some editor.
// Set up the canvas to be drawn onto.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

// Ensure that the canvas can fit the entire rendering
const viewport = editor.image.getImportExportViewport();
canvas.width = viewport.getScreenRectSize().x;
canvas.height = viewport.getScreenRectSize().y;

// Render editor.image onto the renderer
const renderer = new CanvasRenderer(ctx, viewport);
editor.image.render(renderer, viewport);

// Add the rendered canvas to the document.
document.body.appendChild(canvas);

Example 44

From page: classes/js-draw.Color4.html

import { Color4 } from '@js-draw/math';

console.log('Red:', Color4.fromString('#f00'));
console.log('Also red:', Color4.ofRGB(1, 0, 0), Color4.red);
console.log('Mixing red and blue:', Color4.red.mix(Color4.blue, 0.5));
console.log('To string:', Color4.orange.toHexString());

Example 45

From page: classes/js-draw.Color4.html

import { Color4 } from '@js-draw/math';
console.log(Color4.fromHex('#ff0'));

Example 46

From page: classes/js-draw.Editor.html

import { Editor } from 'js-draw';

const editor = new Editor(document.body);

const toolbar = editor.addToolbar();
toolbar.addSaveButton(() => {
  const saveData = editor.toSVG().outerHTML;
  // Do something with saveData...
});

Example 47

From page: classes/js-draw.Editor.html

import { Editor } from 'js-draw';

const container = document.body;

// Create an editor
const editor = new Editor(container, {
  // 2e-10 and 1e12 are the default values for minimum/maximum zoom.
  minZoom: 2e-10,
  maxZoom: 1e12,
});

// Add the default toolbar
const toolbar = editor.addToolbar();

const createCustomIcon = () => {
  // Create/return an icon here.
};

// Add a custom button
toolbar.addActionButton({
  label: 'Custom Button'
  icon: createCustomIcon(),
}, () => {
  // Do something here
});

Example 48

From page: classes/js-draw.Editor.html

import { Editor, Stroke, Path, Color4, pathToRenderable } from 'js-draw';
const editor = new Editor(document.body);

// Create a path.
const stroke = new Stroke([
  pathToRenderable(Path.fromString('M0,0 L100,100 L300,30 z'), { fill: Color4.red }),
]);
const addComponentCommand = editor.image.addComponent(stroke);

// Add the stroke to the editor
editor.dispatch(addComponentCommand);

Example 49

From page: classes/js-draw.Editor.html

import { Editor, EditorEventType, SerializableCommand } from 'js-draw';

// Create a minimal editor
const editor = new Editor(document.body);
editor.addToolbar();

// Create a place to show text output
const log = document.createElement('textarea');
document.body.appendChild(log);
log.style.width = '100%';
log.style.height = '200px';

// Listen for CommandDone events (there's also a CommandUndone)
editor.notifier.on(EditorEventType.CommandDone, event => {
  // Type narrowing for TypeScript -- event will always be of kind CommandDone,
  // but TypeScript doesn't know this.
  if (event.kind !== EditorEventType.CommandDone) return;

  log.value = `Command done ${event.command.description(editor, editor.localization)}\n`;

  if (event.command instanceof SerializableCommand) {
    log.value += `serializes to: ${JSON.stringify(event.command.serialize())}`;
  }
});

// Dispatch an initial command to trigger the event listener for the first time
editor.dispatch(editor.image.setAutoresizeEnabled(true));

Example 50

From page: classes/js-draw.Editor.html

import {
	Editor, EditorImage, Stroke, Path, Color4,
} from 'js-draw';

const editor = new Editor(document.body);

const stroke = Stroke.fromFilled(
	Path.fromString('m0,0 l100,100 l0,-10 z'),
	Color4.red,
);
editor.dispatch(EditorImage.addComponent(stroke));

Example 51

From page: classes/js-draw.Editor.html

import {Editor} from 'js-draw';
const editor = new Editor(document.body);

---visible---
await editor.loadFromSVG(`
  <svg viewBox="5 23 52 30" width="52" height="16" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
    <text style="
      transform: matrix(0.181846, 0.1, 0, 0.181846, 11.4, 33.2);
      font-family: serif;
      font-size: 32px;
      fill: rgb(100, 140, 61);
    ">An SVG image!</text>
  </svg>
`);

Example 52

From page: classes/js-draw.Editor.html

import { Editor, Color4, BackgroundComponentBackgroundType } from 'js-draw';
const editor = new Editor(document.body);
editor.dispatch(editor.setBackgroundStyle({
    color: Color4.orange,
    type: BackgroundComponentBackgroundType.Grid,
    autoresize: true,
}));

Example 53

From page: classes/js-draw.Editor.html

import { Editor, ImageComponent, Mat33 } from 'js-draw';
const editor = new Editor(document.body);

//
// Adding an image
//
const myHtmlImage = new Image();
myHtmlImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAAKklEQVQIW2Ns022zZGRgfPnz8s8HDQwN/xgZgKBDu0PuL8tf5d8/fz8FAOiDD1H2gfpGAAAAAElFTkSuQmCC';

const rotated45Degrees = Mat33.zRotation(Math.PI / 4); // A 45 degree = pi/4 radian rotation
const scaledByFactorOf100 = Mat33.scaling2D(100);
// Scale **and** rotate
const transform = rotated45Degrees.rightMul(scaledByFactorOf100);

const imageComponent = await ImageComponent.fromImage(myHtmlImage, transform);
await editor.dispatch(editor.image.addComponent(imageComponent));

//
// Make a new image from the editor itself (with editor.toDataURL)
//
const toolbar = editor.addToolbar();
toolbar.addActionButton('From editor', async () => {
	const dataUrl = editor.toDataURL();
	const htmlImage = new Image();
	htmlImage.src = dataUrl;

	const imageComponent = await ImageComponent.fromImage(htmlImage, Mat33.identity);
	await editor.addAndCenterComponents([ imageComponent ]);
});

Example 54

From page: classes/js-draw.EditorImage.html

import { Editor, RenderingStyle, Erase, Stroke, pathToRenderable } from 'js-draw';
import { Path, Color4, Point2, Vec2, Rect2 } from '@js-draw/math';
const editor = new Editor(document.body);


// //////////////// //
// Helper functions //
// //////////////// //

function addStroke(path: Path, style: RenderingStyle) {
	const stroke = new Stroke([
		pathToRenderable(path, style)
	]);

	// Create a command that adds the stroke to the image
	// (but don't apply it yet).
	const command = editor.image.addComponent(stroke);
	// Actually apply the command.
	editor.dispatch(command);
}

function addBoxAt(point: Point2, color: Color4) {
	// Create a 10x10 square at the given point:
	const box = new Rect2(point.x, point.y, 10, 10);

	addStroke(
		Path.fromRect(box),
		{ fill: color },
	);
}


function makeTrashIcon() {
	const container = document.createElement('div');
	container.textContent = '🗑️';
	return container;
}


// //////////////////// //
// End helper functions //
// //////////////////// //



// Add some components to the image:
addBoxAt(Vec2.of(0, 0), Color4.green);
addBoxAt(Vec2.of(20, 0), Color4.orange);
addBoxAt(Vec2.of(20, 20), Color4.blue);

// Get the components in a small rectangle near (0, 0)
const components = editor.image.getComponentsIntersecting(
	new Rect2(0, 0, 5, 5), // a 5x5 square with top left (0, 0)
);


// Add a button that removes the components
const toolbar = editor.addToolbar();
toolbar.addActionButton({
	icon: makeTrashIcon(),
	label: 'remove near (0,0)',
}, () => {
	const deleteCommand = new Erase(components);
	editor.dispatch(deleteCommand);
});

Example 55

From page: classes/js-draw.EditorImage.html

import {Editor,CanvasRenderer} from 'js-draw';

// Create an editor and load initial data -- don't add to the body (hidden editor).
const editor = new Editor(document.createElement('div'));
await editor.loadFromSVG('<svg><path d="m0,0 l100,5 l-50,60 l30,20 z" fill="green"/></svg>');
---visible---
// Given some editor.
// Set up the canvas to be drawn onto.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

// Ensure that the canvas can fit the entire rendering
const viewport = editor.image.getImportExportViewport();
canvas.width = viewport.getScreenRectSize().x;
canvas.height = viewport.getScreenRectSize().y;

// Render editor.image onto the renderer
const renderer = new CanvasRenderer(ctx, viewport);
editor.image.render(renderer, viewport);

// Add the rendered canvas to the document.
document.body.appendChild(canvas);

Example 56

From page: classes/js-draw.EditorImage.html

import { Editor } from 'js-draw';

const editor = new Editor(document.body);
const toolbar = editor.addToolbar();

// Add a save button to demonstrate what the output looks like
// (it should change size to fit whatever was drawn)
toolbar.addSaveButton(() => {
  document.body.replaceChildren(editor.toSVG({ sanitize: true }));
});

// Actually using setAutoresizeEnabled:
//
// To set autoresize without announcing for accessibility/making undoable
const addToHistory = false;
editor.dispatchNoAnnounce(editor.image.setAutoresizeEnabled(true), addToHistory);

// Add to undo history **and** announce for accessibility
//editor.dispatch(editor.image.setAutoresizeEnabled(true), true);

Example 57

From page: classes/js-draw.EditorImage.html

import {
	Editor, EditorImage, Stroke, Path, Color4,
} from 'js-draw';

const editor = new Editor(document.body);

const stroke = Stroke.fromFilled(
	Path.fromString('m0,0 l100,100 l0,-10 z'),
	Color4.red,
);
editor.dispatch(EditorImage.addComponent(stroke));

Example 58

From page: classes/js-draw.Erase.html

import { Editor, Erase, uniteCommands, Color4, Path, Stroke, Rect2, pathToRenderable } from 'js-draw';

const editor = new Editor(document.body);
editor.addToolbar();

// Add a large number of strokes
const commands = [];
for (let x = -20; x < 20; x++) {
  for (let y = 0; y < 60; y++) {
    const stroke = new Stroke([
      pathToRenderable(
        Path.fromString(`m${x * 5},${y * 5}l1,1`),
        { fill: Color4.transparent, stroke: {width: 2, color: Color4.ofRGB(x / 10, y / 10, 0.5)}} )
      ]);
    commands.push(editor.image.addElement(stroke));
  }
}
await editor.dispatch(uniteCommands(commands, 100));

---visible---
// Given some editor...

// Find all elements intersecting the rectangle with top left (-10,-30) and
// (width,height)=(50,100).
const elems = editor.image.getComponentsIntersecting(
	new Rect2(-10, -30, 50, 100)
);

// Create a command that erases [elems] when applied
const eraseElemsCmd = new Erase(elems);

// Apply the command (and make it undoable)
editor.dispatch(eraseElemsCmd);

Example 59

From page: classes/js-draw.IconProvider.html

import * as jsdraw from 'js-draw';

class CustomIconProvider extends jsdraw.IconProvider {
    // Use '☺' instead of the default dropdown symbol.
    public override makeDropdownIcon() {
        const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        icon.innerHTML = `
            <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
        `;
        icon.setAttribute('viewBox', '0 0 100 100');
        return icon;
    }
}

const icons = new CustomIconProvider();
const editor = new jsdraw.Editor(document.body, {
    // The icon pack to use is specified through the editor's initial
    // configuration object:
    iconProvider: icons,
});

// Add a toolbar that uses these icons
jsdraw.makeDropdownToolbar(editor).addDefaults();

Example 60

From page: classes/js-draw.ImageComponent.html

import { Editor, ImageComponent, Mat33 } from 'js-draw';
const editor = new Editor(document.body);

//
// Adding an image
//
const myHtmlImage = new Image();
myHtmlImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAAKklEQVQIW2Ns022zZGRgfPnz8s8HDQwN/xgZgKBDu0PuL8tf5d8/fz8FAOiDD1H2gfpGAAAAAElFTkSuQmCC';

const rotated45Degrees = Mat33.zRotation(Math.PI / 4); // A 45 degree = pi/4 radian rotation
const scaledByFactorOf100 = Mat33.scaling2D(100);
// Scale **and** rotate
const transform = rotated45Degrees.rightMul(scaledByFactorOf100);

const imageComponent = await ImageComponent.fromImage(myHtmlImage, transform);
await editor.dispatch(editor.image.addComponent(imageComponent));

//
// Make a new image from the editor itself (with editor.toDataURL)
//
const toolbar = editor.addToolbar();
toolbar.addActionButton('From editor', async () => {
	const dataUrl = editor.toDataURL();
	const htmlImage = new Image();
	htmlImage.src = dataUrl;

	const imageComponent = await ImageComponent.fromImage(htmlImage, Mat33.identity);
	await editor.addAndCenterComponents([ imageComponent ]);
});

Example 61

From page: classes/js-draw.InsertImageWidget.html

import { Editor, makeEdgeToolbar, InsertImageWidget } from 'js-draw';

const editor = new Editor(document.body);
const toolbar = makeEdgeToolbar(editor);

toolbar.addWidget(new InsertImageWidget(editor));

Example 62

From page: classes/js-draw.LineSegment2.html

import {LineSegment2, Vec2} from '@js-draw/math';
const l = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 2));
console.log('length: ', l.length);
console.log('direction: ', l.direction);
console.log('bounding box: ', l.bbox);

Example 63

From page: classes/js-draw.LineSegment2.html

import {LineSegment2, Vec2} from '@js-draw/math';
console.log(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 0), Vec2.of(0, 1)]));

Example 64

From page: classes/js-draw.Mat33.html

import {Mat33, Vec2} from '@js-draw/math';

const moveLeftAndUp = Mat33.translation(Vec2.of(5, 6));
console.log(moveLeftAndUp);

Example 65

From page: classes/js-draw.Mat33.html

---use-previous---
---visible---
console.log(moveLeftAndUp.transformVec2(Vec2.of(1, 1)));
console.log(moveLeftAndUp.transformVec2(Vec2.of(-1, 2)));

Example 66

From page: classes/js-draw.Mat33.html

---use-previous---
---visible---
// Create a matrix by right multiplying.
const scaleThenRotate =
  // The resultant matrix first scales by a factor of two
  Mat33.scaling2D(2).rightMul(
    // ...then rotates by pi/2 radians = 90 degrees.
    Mat33.zRotation(Math.PI / 2)
  );
console.log(scaleThenRotate);

// Use scaleThenRotate to scale then rotate a vector.
console.log(scaleThenRotate.transformVec2(Vec2.unitX));

Example 67

From page: classes/js-draw.Mat33.html

import {Mat33, Vec2} from '@js-draw/math';
console.log(Mat33.identity.rightMul(Mat33.identity));

// Create a matrix by right multiplying.
const scaleThenRotate =
  // The resultant matrix first scales by a factor of two
  Mat33.scaling2D(2).rightMul(
    // ...then rotates by pi/4 radians = 45 degrees.
    Mat33.zRotation(Math.PI / 4)
  );
console.log(scaleThenRotate);

// Use scaleThenRotate to scale then rotate a vector.
console.log(scaleThenRotate.transformVec2(Vec2.unitX));

Example 68

From page: classes/js-draw.Mat33.html

import { Mat33 } from '@js-draw/math';
console.log(
  new Mat33(
    1, 2, 3,
    4, 5, 6,
    7, 8, 9,
  )
);

Example 69

From page: classes/js-draw.Mat33.html

import { Mat33 } from '@js-draw/math';
console.log(Mat33.identity.toString());

Example 70

From page: classes/js-draw.Mat33.html

import { Mat33, Vec2 } from '@js-draw/math';

const halfCircle = Math.PI; // PI radians = 180 degrees = 1/2 circle
const center = Vec2.of(1, 1); // The point (1,1)
const rotationMatrix = Mat33.zRotation(halfCircle, center);

console.log(
  'Rotating (0,0) 180deg about', center, 'results in',
  // Rotates (0,0)
  rotationMatrix.transformVec2(Vec2.zero),
);

Example 71

From page: classes/js-draw.PanZoomTool.html

import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';

const editor = new Editor(document.body);

// By default, there are multiple PanZoom tools that handle different events.
// This gets all PanZoomTools.
const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);

// The first PanZoomTool is the highest priority -- by default,
// this tool is responsible for handling multi-finger touch gestures.
//
// Lower-priority PanZoomTools handle one-finger touch gestures and
// key-presses.
const panZoomTool = panZoomToolList[0];

// Lock rotation for multi-finger touch gestures.
panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);

Example 72

From page: classes/js-draw.Path.html

import {Path, Mat33, Vec2, LineSegment2} from '@js-draw/math';

// Creates a path from an SVG path string.
// In this case,
// 1. Move to (0,0)
// 2. Line to (100,0)
const path = Path.fromString('M0,0 L100,0');

// Logs the distance from (10,0) to the curve 1 unit
// away from path. This curve forms a stroke with the path at
// its center.
const strokeRadius = 1;
console.log(path.signedDistance(Vec2.of(10,0), strokeRadius));

// Log a version of the path that's scaled by a factor of 4.
console.log(path.transformedBy(Mat33.scaling2D(4)).toString());

// Log all intersections of a stroked version of the path with
// a vertical line segment.
// (Try removing the `strokeRadius` parameter).
const segment = new LineSegment2(Vec2.of(5, -100), Vec2.of(5, 100));
console.log(path.intersection(segment, strokeRadius).map(i => i.point));

Example 73

From page: classes/js-draw.Path.html

import {Path} from '@js-draw/math';
console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0

Example 74

From page: classes/js-draw.Path.html

import {Path, Vec2} from '@js-draw/math';
console.log(Path.fromString('m0,0 L100,0').signedDistance(Vec2.zero, 1));

Example 75

From page: classes/js-draw.Path.html

import { Path } from '@js-draw/math';

const path = Path.fromString('m0,0l100,100');
console.log(path.toString(true)); // true: Prefer relative to absolute path commands

Example 76

From page: classes/js-draw.PenTool.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 77

From page: classes/js-draw.QuadraticBezier.html

import { QuadraticBezier, Vec2 } from '@js-draw/math';

const startPoint = Vec2.of(4, 3);
const controlPoint = Vec2.of(1, 1);
const endPoint = Vec2.of(1, 3);

const curve = new QuadraticBezier(
  startPoint,
  controlPoint,
  endPoint,
);

console.log('Curve:', curve);

Example 78

From page: classes/js-draw.Rect2.html

import { Rect2, Vec2 } from '@js-draw/math';

const rect = Rect2.fromCorners(
  Vec2.of(0, 0),
  Vec2.of(10, 10),
);
console.log('area', rect.area);
console.log('topLeft', rect.topLeft);

Example 79

From page: classes/js-draw.Stroke.html

import {
	Editor, EditorImage, Stroke, Path, Color4,
} from 'js-draw';

const editor = new Editor(document.body);

const stroke = Stroke.fromFilled(
	Path.fromString('m0,0 l100,100 l0,-10 z'),
	Color4.red,
);
editor.dispatch(EditorImage.addComponent(stroke));

Example 80

From page: classes/js-draw.Stroke.html

import { Editor, Stroke, Color4 } from 'js-draw';
const editor = new Editor(document.body);
---visible---
const stroke = Stroke.fromStroked('m0,0 l10,10', { width: 10, color: Color4.red });
editor.dispatch(editor.image.addComponent(stroke));

Example 81

From page: classes/js-draw.TextComponent.html

import { Editor, TextComponent, Mat33, Vec2, Color4, TextRenderingStyle } from 'js-draw';
const editor = new Editor(document.body);
editor.dispatch(editor.setBackgroundStyle({ color: Color4.black, autoresize: true ));
---visible---
/// Adding a simple TextComponent
///------------------------------

const positioning1 = Mat33.translation(Vec2.of(10, 10));
const style: TextRenderingStyle = {
    fontFamily: 'sans', size: 12, renderingStyle: { fill: Color4.green },
};

editor.dispatch(
    editor.image.addComponent(new TextComponent(['Hello, world'], positioning1, style)),
);


/// Adding nested TextComponents
///-----------------------------

// Add another TextComponent that contains text and a TextComponent. Observe that '[Test]'
// is placed directly after 'Test'.
const positioning2 = Mat33.translation(Vec2.of(10, 50));
editor.dispatch(
    editor.image.addComponent(
        new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
    ),
);

Example 82

From page: classes/js-draw.Viewport.html

import { Editor, Viewport, Mat33, Vec2 } from 'js-draw';
const editor = new Editor(document.body);
const moveRight = Mat33.translation(Vec2.unitX.times(500));
// Move the **canvas** right by 500 units:
Viewport.transformBy(moveRight).apply(editor);

Example 83

From page: interfaces/js-draw.EditorSettings.html

import { EditorSettings, Editor, KeyBinding, makeEdgeToolbar } from 'js-draw';
import { MaterialIconProvider } from '@js-draw/material-icons';

// All settings are optional! Try commenting them out.
const settings: EditorSettings = {
	// Use a non-default set of icons
	iconProvider: new MaterialIconProvider(),

	// Only capture mouse wheel events if the editor has focus. This is useful
	// when the editor is part of a larger, scrolling page.
	wheelEventsEnabled: 'only-if-focused',

	// The default minimum zoom is 2e-10...
	minZoom: 2e-10,

	// and the maximum default zoom is 1e12
	maxZoom: 1e12,

	// Override some keyboard shortcuts!
	keyboardShortcutOverrides: {
		// The ID for the save action
		'jsdraw.toolbar.SaveActionWidget.save': [
			// "Meta" = the command key on MacOS
			KeyBinding.fromString('ctrlOrMeta+s'),

			// Also map ctrl+M to save!
			KeyBinding.fromString('ctrl+m'),
		],
	},
};

// Create the editor!
const editor = new Editor(document.body, settings);

// Selects a specific toolbar type. See also makeDropdownToolbar
const toolbar = makeEdgeToolbar(editor);
toolbar.addDefaults();

// Add the action button that is triggered by the save keyboard shortcuts above.
toolbar.addSaveButton(() => {
	const saveData = editor.toSVG().outerHTML;

	// Do something with saveData
	alert('Saved data:\n\n' + saveData);
});

Example 84

From page: interfaces/js-draw.EditorSettings.html

import { Editor, makePolylineBuilder } from 'js-draw';

const editor = new Editor(document.body, {
	pens: {
		additionalPenTypes: [{
			name: 'Polyline (For debugging)',
			id: 'custom-polyline',
			factory: makePolylineBuilder,

			// The pen doesn't create fixed shapes (e.g. squares, rectangles, etc)
			// and so should go under the "pens" section.
			isShapeBuilder: false,
		}],
	},
});
editor.addToolbar();

Example 85

From page: interfaces/js-draw.EditorSettings.html

import {Editor} from 'js-draw';
const editor = new Editor(document.body, {
  // Only allow selecting the polyline pen from the toolbar.
  pens: { filterPenTypes: p => p.id === 'polyline-pen' },
});
editor.addToolbar();

Example 86

From page: interfaces/js-draw.Vec3-1.html

import { Vec3 } from '@js-draw/math';

console.log('Vector addition:', Vec3.of(1, 2, 3).plus(Vec3.of(0, 1, 0)));
console.log('Scalar multiplication:', Vec3.of(1, 2, 3).times(2));
console.log('Cross products:', Vec3.unitX.cross(Vec3.unitY));
console.log('Magnitude:', Vec3.of(1, 2, 3).length(), 'or', Vec3.of(1, 2, 3).magnitude());
console.log('Square Magnitude:', Vec3.of(1, 2, 3).magnitudeSquared());
console.log('As an array:', Vec3.unitZ.asArray());

Example 87

From page: interfaces/js-draw.Vec3-1.html

import { Vec2 } from '@js-draw/math';
console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
console.log(Vec2.of(-1, 0).angle());  // atan2(0, -1)

Example 88

From page: interfaces/js-draw.Vec3-1.html

import { Vec3 } from '@js-draw/math';
console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)

Example 89

From page: interfaces/js-draw.Vec3-1.html

import { Vec3 } from '@js-draw/math';
console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10

Example 90

From page: functions/js-draw.adjustEditorThemeForContrast.html

import { Editor, adjustEditorThemeForContrast } from 'js-draw';

const editor = new Editor(document.body);
editor.addToolbar();

const css = `
  :root .imageEditorContainer {
    --background-color-1: #ffff77;
    --foreground-color-1: #fff;
    --background-color-2: #ffff99;
    --foreground-color-2: #ffff88;
    --background-color-3: #ddffff;
    --foreground-color-3: #eeffff;
    --selection-background-color: #9f7;
    --selection-foreground-color: #98f;
  }

  @media screen and (prefers-color-scheme: dark) {
    :root .imageEditorContainer {
      --background-color-1: black;
    }
  }
`;
editor.addStyleSheet(css);

adjustEditorThemeForContrast(editor);

// Because adjustEditorThemeForContrast overrides the current theme, it should be called again
// to allow the editor to switch between light/dark themes.
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
  adjustEditorThemeForContrast(editor);
});

window.matchMedia('print').addEventListener('change', () => {
  adjustEditorThemeForContrast(editor);
});

Example 91

From page: functions/js-draw.makeArrowBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 92

From page: functions/js-draw.makeDropdownToolbar.html

import { makeDropdownToolbar, Editor } from 'js-draw';

const editor = new Editor(document.body);
const toolbar = makeDropdownToolbar(editor);
toolbar.addDefaults();

toolbar.addExitButton(editor => {
  // TODO
});

toolbar.addSaveButton(editor => {
  // TODO
});

Example 93

From page: functions/js-draw.makeEdgeToolbar.html

import { makeEdgeToolbar, Editor } from 'js-draw';

const editor = new Editor(document.body);
const toolbar = makeEdgeToolbar(editor);
toolbar.addDefaults();

toolbar.addSaveButton(editor => {
  // TODO
});

toolbar.addExitButton(editor => {
  // TODO
});

Example 94

From page: functions/js-draw.makeFilledRectangleBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 95

From page: functions/js-draw.makeFreehandLineBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 96

From page: functions/js-draw.makeLineBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 97

From page: functions/js-draw.makeOutlinedCircleBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 98

From page: functions/js-draw.makeOutlinedRectangleBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 99

From page: functions/js-draw.makePolylineBuilder.html

import {
	Editor,
	PenTool,
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
	InputEvtType,
	sendPenEvent,
} from 'js-draw';
import { Color4, Vec2 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Different pen types that build strokes in different ways. This is a list of some of the
// default ones:
const strokeBuilders = [
	makePolylineBuilder,
	makeOutlinedCircleBuilder,
	makeOutlinedRectangleBuilder,
	makeArrowBuilder,
	makePressureSensitiveFreehandLineBuilder,
	makeFreehandLineBuilder,
];

// Get the first pen
const pen = editor.toolController.getMatchingTools(PenTool)[0];
// If using a different pen (e.g. the second), be sure to select it!
// pen.setEnabled(true);

// Draw something with each style
for (const factory of strokeBuilders) {
	// Make the pen use a certain style.
	pen.setStrokeFactory(factory);
	// What happens if the following line is uncommented?
	// pen.setStrokeFactory(makeArrowBuilder);

	// Select a random pen color
	const penColor = Color4.ofRGB(Math.random(), Math.random(), Math.random());
	pen.setColor(penColor);

	// Draw something!
	const imageSize = editor.getImportExportRect().size;
	const startPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	const endPos = Vec2.of(Math.random() * imageSize.x, Math.random() * imageSize.y);
	sendPenEvent(editor, InputEvtType.PointerDownEvt, startPos);
	sendPenEvent(editor, InputEvtType.PointerMoveEvt, startPos.lerp(endPos, 0.5));
	sendPenEvent(editor, InputEvtType.PointerUpEvt, endPos);
}

Example 100

From page: functions/js-draw.toRoundedString.html

import { toRoundedString } from '@js-draw/math';

console.log('Rounded: ', toRoundedString(1.000000011));

Example 101

From page: functions/js-draw.uniteCommands.html

import { Editor, pathToRenderable, Stroke, uniteCommands } from 'js-draw';
import { Path, Color4 } from '@js-draw/math';

const editor = new Editor(document.body);
editor.addToolbar();

// Create strokes!
const strokes = [];
for (let i = 0; i < 10; i++) {
  const renderablePath = pathToRenderable(
    Path.fromString(`M0,${i * 10} L100,100 L300,30 z`),
    { fill: Color4.transparent, stroke: { color: Color4.red, width: 1, } }
  );
  strokes.push(new Stroke([ renderablePath ]));
}

// Convert to commands
const addStrokesCommands = strokes.map(stroke => editor.image.addElement(stroke));

// Apply all as a single undoable command (try applying each in a loop instead!)
await editor.dispatch(uniteCommands(addStrokesCommands));

// The second parameter to uniteCommands is for very large numbers of commands, when
// applying them shouldn't be done all at once (which would block the UI).

// The second parameter to uniteCommands is for very large numbers of commands, when
// applying them shouldn't be done all at once (which would block the UI).

Example 102

From page: modules/_js-draw_material-icons.html

import { Editor, makeEdgeToolbar } from 'js-draw';
import { MaterialIconProvider } from '@js-draw/material-icons';

// Apply js-draw CSS
import 'js-draw/styles';

const editor = new Editor(document.body, {
  iconProvider: new MaterialIconProvider(),
});

// Ensure that there is enough room for the toolbar
editor.getRootElement().style.minHeight = '500px';

// Add a toolbar
const toolbar = makeEdgeToolbar(editor);

// ...with the default elements
toolbar.addDefaults();

Example 103

From page: functions/_js-draw_material-icons.makeMaterialIconProviderClass.html

import * as jsdraw from 'js-draw';
import { makeMaterialIconProviderClass } from '@js-draw/material-icons';
const MaterialIconProvider = makeMaterialIconProviderClass(jsdraw);

(new jsdraw.Editor(
    document.body, { iconProvider: new MaterialIconProvider() },
)).addToolbar();

Example 104

From page: modules/_js-draw_math.html

import { Vec2, Mat33, Rect2 } from '@js-draw/math';

// Example: Rotate a vector 90 degrees about the z-axis
const rotate90Degrees = Mat33.zRotation(Math.PI/2); // π/2 radians = 90 deg
const moveUp = Mat33.translation(Vec2.of(1, 0));
const moveUpThenRotate = rotate90Degrees.rightMul(moveUp);
console.log(moveUpThenRotate.transformVec2(Vec2.of(1, 2)));

// Example: Bounding box of some points
console.log(Rect2.bboxOf([
  Vec2.of(1, 2), Vec2.of(3, 4), Vec2.of(-100, 1000),
]));

Example 105

From page: modules/_js-draw_math.Vec2.html

import { Vec2 } from '@js-draw/math';

const v = Vec2.of(1, 2);
console.log('a Vec2:', v);
console.log('x component:', v.x);
console.log('z component:', v.z);

Example 106

From page: functions/_js-draw_math.Vec2.of.html

import { Vec2 } from '@js-draw/math';
const v = Vec2.of(3, 4); // x=3, y=4.

Example 107

From page: functions/_js-draw_math.Vec2.ofXY.html

import { Vec2 } from '@js-draw/math';
const v1 = Vec2.ofXY({ x: 3, y: 4.5 });
const v2 = Vec2.ofXY({ x: -123.4, y: 1 });

Example 108

From page: functions/_js-draw_math.Vec3.of.html

import { Vec3 } from '@js-draw/math';
const v1 = Vec3.of(1, 2, 3);
console.log(v1.plus(Vec3.of(0, 100, 0)));

Example 109

From page: classes/_js-draw_math.Color4.html

import { Color4 } from '@js-draw/math';

console.log('Red:', Color4.fromString('#f00'));
console.log('Also red:', Color4.ofRGB(1, 0, 0), Color4.red);
console.log('Mixing red and blue:', Color4.red.mix(Color4.blue, 0.5));
console.log('To string:', Color4.orange.toHexString());

Example 110

From page: classes/_js-draw_math.Color4.html

import { Color4 } from '@js-draw/math';
console.log(Color4.fromHex('#ff0'));

Example 111

From page: classes/_js-draw_math.LineSegment2.html

import {LineSegment2, Vec2} from '@js-draw/math';
const l = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 2));
console.log('length: ', l.length);
console.log('direction: ', l.direction);
console.log('bounding box: ', l.bbox);

Example 112

From page: classes/_js-draw_math.LineSegment2.html

import {LineSegment2, Vec2} from '@js-draw/math';
console.log(LineSegment2.ofSmallestContainingPoints([Vec2.of(1, 0), Vec2.of(0, 1)]));

Example 113

From page: classes/_js-draw_math.Mat33.html

import {Mat33, Vec2} from '@js-draw/math';

const moveLeftAndUp = Mat33.translation(Vec2.of(5, 6));
console.log(moveLeftAndUp);

Example 114

From page: classes/_js-draw_math.Mat33.html

---use-previous---
---visible---
console.log(moveLeftAndUp.transformVec2(Vec2.of(1, 1)));
console.log(moveLeftAndUp.transformVec2(Vec2.of(-1, 2)));

Example 115

From page: classes/_js-draw_math.Mat33.html

---use-previous---
---visible---
// Create a matrix by right multiplying.
const scaleThenRotate =
  // The resultant matrix first scales by a factor of two
  Mat33.scaling2D(2).rightMul(
    // ...then rotates by pi/2 radians = 90 degrees.
    Mat33.zRotation(Math.PI / 2)
  );
console.log(scaleThenRotate);

// Use scaleThenRotate to scale then rotate a vector.
console.log(scaleThenRotate.transformVec2(Vec2.unitX));

Example 116

From page: classes/_js-draw_math.Mat33.html

import {Mat33, Vec2} from '@js-draw/math';
console.log(Mat33.identity.rightMul(Mat33.identity));

// Create a matrix by right multiplying.
const scaleThenRotate =
  // The resultant matrix first scales by a factor of two
  Mat33.scaling2D(2).rightMul(
    // ...then rotates by pi/4 radians = 45 degrees.
    Mat33.zRotation(Math.PI / 4)
  );
console.log(scaleThenRotate);

// Use scaleThenRotate to scale then rotate a vector.
console.log(scaleThenRotate.transformVec2(Vec2.unitX));

Example 117

From page: classes/_js-draw_math.Mat33.html

import { Mat33 } from '@js-draw/math';
console.log(
  new Mat33(
    1, 2, 3,
    4, 5, 6,
    7, 8, 9,
  )
);

Example 118

From page: classes/_js-draw_math.Mat33.html

import { Mat33 } from '@js-draw/math';
console.log(Mat33.identity.toString());

Example 119

From page: classes/_js-draw_math.Mat33.html

import { Mat33, Vec2 } from '@js-draw/math';

const halfCircle = Math.PI; // PI radians = 180 degrees = 1/2 circle
const center = Vec2.of(1, 1); // The point (1,1)
const rotationMatrix = Mat33.zRotation(halfCircle, center);

console.log(
  'Rotating (0,0) 180deg about', center, 'results in',
  // Rotates (0,0)
  rotationMatrix.transformVec2(Vec2.zero),
);

Example 120

From page: classes/_js-draw_math.Path.html

import {Path, Mat33, Vec2, LineSegment2} from '@js-draw/math';

// Creates a path from an SVG path string.
// In this case,
// 1. Move to (0,0)
// 2. Line to (100,0)
const path = Path.fromString('M0,0 L100,0');

// Logs the distance from (10,0) to the curve 1 unit
// away from path. This curve forms a stroke with the path at
// its center.
const strokeRadius = 1;
console.log(path.signedDistance(Vec2.of(10,0), strokeRadius));

// Log a version of the path that's scaled by a factor of 4.
console.log(path.transformedBy(Mat33.scaling2D(4)).toString());

// Log all intersections of a stroked version of the path with
// a vertical line segment.
// (Try removing the `strokeRadius` parameter).
const segment = new LineSegment2(Vec2.of(5, -100), Vec2.of(5, 100));
console.log(path.intersection(segment, strokeRadius).map(i => i.point));

Example 121

From page: classes/_js-draw_math.Path.html

import {Path} from '@js-draw/math';
console.log(Path.fromString('m0,0l1,1').reversed()); // -> M1,1 L0,0

Example 122

From page: classes/_js-draw_math.Path.html

import {Path, Vec2} from '@js-draw/math';
console.log(Path.fromString('m0,0 L100,0').signedDistance(Vec2.zero, 1));

Example 123

From page: classes/_js-draw_math.Path.html

import { Path } from '@js-draw/math';

const path = Path.fromString('m0,0l100,100');
console.log(path.toString(true)); // true: Prefer relative to absolute path commands

Example 124

From page: classes/_js-draw_math.QuadraticBezier.html

import { QuadraticBezier, Vec2 } from '@js-draw/math';

const startPoint = Vec2.of(4, 3);
const controlPoint = Vec2.of(1, 1);
const endPoint = Vec2.of(1, 3);

const curve = new QuadraticBezier(
  startPoint,
  controlPoint,
  endPoint,
);

console.log('Curve:', curve);

Example 125

From page: classes/_js-draw_math.Rect2.html

import { Rect2, Vec2 } from '@js-draw/math';

const rect = Rect2.fromCorners(
  Vec2.of(0, 0),
  Vec2.of(10, 10),
);
console.log('area', rect.area);
console.log('topLeft', rect.topLeft);

Example 126

From page: interfaces/_js-draw_math.Vec3-1.html

import { Vec3 } from '@js-draw/math';

console.log('Vector addition:', Vec3.of(1, 2, 3).plus(Vec3.of(0, 1, 0)));
console.log('Scalar multiplication:', Vec3.of(1, 2, 3).times(2));
console.log('Cross products:', Vec3.unitX.cross(Vec3.unitY));
console.log('Magnitude:', Vec3.of(1, 2, 3).length(), 'or', Vec3.of(1, 2, 3).magnitude());
console.log('Square Magnitude:', Vec3.of(1, 2, 3).magnitudeSquared());
console.log('As an array:', Vec3.unitZ.asArray());

Example 127

From page: interfaces/_js-draw_math.Vec3-1.html

import { Vec2 } from '@js-draw/math';
console.log(Vec2.of(-1, -0).angle()); // atan2(-0, -1)
console.log(Vec2.of(-1, 0).angle());  // atan2(0, -1)

Example 128

From page: interfaces/_js-draw_math.Vec3-1.html

import { Vec3 } from '@js-draw/math';
console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)

Example 129

From page: interfaces/_js-draw_math.Vec3-1.html

import { Vec3 } from '@js-draw/math';
console.log(Vec3.of(-1, -10, 8).maximumEntryMagnitude()); // -> 10

Example 130

From page: functions/_js-draw_math.toRoundedString.html

import { toRoundedString } from '@js-draw/math';

console.log('Rounded: ', toRoundedString(1.000000011));