Public
Edited
May 2, 2023
Importers
Insert cell
Insert cell
import {
activateTool,
gridPoint,
ensureGroupExistsInPaper,
findItemNameInGroup,
addChildToGroup,
selection,
pointOfPath as point,
hidKids,
tmpKids,
tmpGrp,
highlightPoint,
config
} from "f7064faddbbc3e58"
Insert cell
import { pointInventory as Inv } from "90d0ee0bf9ccdd9b"
Insert cell
import { canvasValues } from "eb11a24ab32ae888"
Insert cell
import { selectToolAction } from "ed819e793c39a3d5"
Insert cell
sketchValue = {
let value = {
drawArrowHead: false,
drawLengthLabel: true,
drawArc: true,
drawArcLabel: true
};
return value;
}
Insert cell
/**
* placeArrow draws the line that user drags from a to b, then places a head to the tip of the line ending to show line's direction.
*/

function placeArrow(start, end, p, value) {
var tempGroup = tmpGrp(p);

let placeArrowHead = function (headPoint, delta) {
var arrowHeadPath = addChildToGroup(
"arrowHeadPath",
new p.Path(),
p,
tempGroup
);
arrowHeadPath.strokeWidth = 2;

var arrowBasePoint = headPoint.subtract(delta.normalize(10));
var arrowLeftPoint = arrowBasePoint.add(delta.normalize(-8).rotate(90));
var arrowRightPoint = arrowBasePoint.add(delta.normalize(8).rotate(90));

arrowHeadPath.strokeColor = "black";
arrowHeadPath.add(arrowLeftPoint);
arrowHeadPath.add(headPoint);
arrowHeadPath.add(arrowRightPoint);
return arrowHeadPath;
};

var mouthDownPath = addChildToGroup(
"mouthDownPath",
new p.Path(),
p,
tempGroup
);
mouthDownPath.strokeWideth = 2;
mouthDownPath.strokeColor = "black";
mouthDownPath.add(start);

mouthDownPath.removeSegment(1);
mouthDownPath.add(end);
var delta = mouthDownPath.segments[1].point.subtract(start);

if (value.drawArrowHead) {
var arrowHeadPath = placeArrowHead(end, delta);
}
}
Insert cell
/**
* placeLengthSymbol function draws the text/number for the line user drags, and draws a bracket to visualize the relationship between
*/
function placeArc(start, end, p, value) {
var tempGroup = ensureGroupExistsInPaper("tempGroup", p);
let placeArcTextLabel = function () {
// if the text is uninitialized, initialize it with a path variable
// if the text is initialized, clear all it's content on the canvas, and reinitialize.
var arcText = addChildToGroup("arcText", new p.PointText(), p, tempGroup);
arcText.point = start.add(through_);
arcText.content = `${Math.trunc(delta.angle * 1000) / 1000.0}`;
return arcText;
};

var delta = end.subtract(start);
var from_ = new p.Point(30, 0);
var through_ = from_.rotate(delta.angle / 2);
var to_ = from_.rotate(delta.angle);

// if the arcPath or arcBasePath is uninitialized, initialize them with a path variable
// if the arcPath or arcBasePath is initialized, clear their path on the canvas, and reinitialize.
var arcPath = addChildToGroup("arcPath", new p.Path.Arc(), p, tempGroup);
var arcBasePath = addChildToGroup("arcBasePath", new p.Path(), p, tempGroup);

arcBasePath.segments = [start, [start.x + 50, start.y]];
arcBasePath.strokeColor = "black";
arcBasePath.dashArray = [1, 1];

arcPath.strokeColor = "black";
arcPath.dashArray = [1, 1];

if (value.drawArcLabel) var arcText = placeArcTextLabel();
}
Insert cell
/**
* placeLengthSymbol function draws the text/number for the line user drags, and draws a bracket to visualize the relationship between
*/
function placeLengthSymbol(start, end, p, value) {
var tempGroup = ensureGroupExistsInPaper("tempGroup", p);
let placeLengthTextLabel = function () {
// if the lengthText is uninitialized, initialize it with a path variable
// if the lengthText is initialized, clear all it's content on the canvas, and reinitialize.
var lengthText = addChildToGroup(
"lengthText",
new p.PointText(),
p,
tempGroup
);
lengthText.point = findItemNameInGroup(
"lengthSymbolPath",
tempGroup
).segments[3].point.add(delta.normalize(8).rotate(90).multiply(sign));
lengthText.content = `${
Math.trunc((delta.length / canvasValues.gridScale) * 1000) / 1000
}`;
lengthText.justification = "center";

return lengthText;
};
var delta = end.subtract(start);
var upDownSegLen = 5;

// if the lengthSymbolPath is uninitialized, initialize it with a path variable
// if the lengthSymbolPath is initialized, clear it's path on the canvas, and reinitialize.
var lengthSymbolPath = addChildToGroup(
"lengthSymbolPath",
new p.Path(),
p,
tempGroup
);

var sign = delta.y > 0 ? 1 : -1;
var upSegment = delta.normalize(upDownSegLen).rotate(45 * sign);
var downSegment = upSegment.rotate(-90 * sign);
var lengthSegment = delta
.divide(2)
.subtract(delta.normalize((2 * upDownSegLen) / Math.sqrt(2)));
var startPoint = start.add(delta.normalize(8).rotate(90).multiply(sign));

lengthSymbolPath.add(startPoint);
lengthSymbolPath.lineBy(upSegment);
lengthSymbolPath.lineBy(lengthSegment);
lengthSymbolPath.lineBy(upSegment);
lengthSymbolPath.lineBy(downSegment);
lengthSymbolPath.lineBy(lengthSegment);
lengthSymbolPath.lineBy(downSegment);
lengthSymbolPath.dashArray = [1, 1];
lengthSymbolPath.strokeColor = "black";

if (value.drawLengthLabel) var lengthText = placeLengthTextLabel();
}
Insert cell
/**
*
*/
function placeMouthDown(event, start, p, id) {
var tempGroup = ensureGroupExistsInPaper("tempGroup", p);
var mainGroup = ensureGroupExistsInPaper("mainGroup", p);
var mainChildren = mainGroup.children;

//mainPath is necessary to be created at this point so the line can be
//drawn continuously as lines drawn to the tempGroup will be removed
if (!mainGroup.children[`mainPath${id}`])
mainGroup.addChild(
new p.Path({
name: `mainPath${id}`,
strokeColor: "black",
strokeWidth: 2
})
);
var mainPath = mainChildren[`mainPath${id}`];
if (mainPath) {
if (mainPath.segments.length > 0) {
start = mainPath.segments[mainPath.segments.length - 1].point;
} else {
start = start ? start : event.point;
}
}

return start;
}
Insert cell
/**
* placeMouthUp function dictates where the line ends, and cleans up the canvas.
*
* @param {*} start - the origin of the line where the user's drag starts, if a line is (a, b), start will be a.
* @param {*} end - the end of the line where the user's drag ends, if a line is (a, b), end will be b.
* @param {*} children - the scope paths
* (mainPath, mouthDownPath, arrowHeadPath, arcPath, lengthSymbolPath, arcBasePath, arcText, lengthText)
* will be cleared from.
*/
function placeMouthUp(start, end, p, id) {
var mainGroup = p.project.activeLayer.children["mainGroup"];
var hiddenGroup = p.project.activeLayer.children["hiddenGroup"];
var tempGroup = p.project.activeLayer.children["tempGroup"];

var mainPath = mainGroup.children[`mainPath${id}`];
let minimumIndex = minIndex(end, mainPath.segments);
if (minimumIndex < 0) {
mainPath.add(start);
mainPath.add(end);
} else if (end.getDistance(mainPath.segments[minimumIndex].point) > 10) {
mainPath.add(end);
} else {
end = mainPath.segments[minimumIndex].point;
mainPath.add(end);
}

tempGroup.removeChildren();

calcGridCordinate(p, hiddenGroup, canvasValues.gridScale, start, end, id);

if (end.x === point(mainPath, 0).x || end.y === point(mainPath, 0).y) {
console.log(mainPath);
mainPath.closed = true;
mainPath.fillColor = config.fillColor;
mainPath.fillColor.alpha = config.fillAlpha;
hidKids(p)[mainPath.name.replace("main", "hidden")].closed = true;
selection.push(mainPath);
Inv.addPath(mainPath, p);
selectToolAction(p);
document.dispatchEvent(new CustomEvent("formChange"));
}
}
Insert cell
function calcGridCordinate(p, hiddenGroup, gridScale, start, end, id) {
var hiddenPath;
var hiddenPathName = `hiddenPath${id}`;
if (!hiddenGroup.children[hiddenPathName])
hiddenPath = addChildToGroup(hiddenPathName, new p.Path(), p, hiddenGroup);

hiddenPath = findItemNameInGroup(hiddenPathName, hiddenGroup);

if (hiddenPath.segments.length > 0) {
hiddenPath.add(gridPoint(end, gridScale, p));
} else {
hiddenPath.add(gridPoint(start, gridScale, p));
hiddenPath.add(gridPoint(end, gridScale, p));
}
}
Insert cell
function minIndex(point, segArray) {
if (segArray.length === 0) {
return -1;
}

let minIdx = segArray.reduce(
(index, _seg, i) =>
point.getDistance(segArray[i].point) <
point.getDistance(segArray[index].point)
? i
: index,
0
);
return minIdx;
}
Insert cell
function sketchToolAction(p) {
var sketchTool = activateTool("sketchTool", p);
var start, end;
var pathId = Date.now();
selection.clear(p);
document.dispatchEvent(new CustomEvent("formChange"));

sketchTool.onMouseMove = function (event) {
tmpGrp(p).removeChildren();
start = null;
var highLightP = Inv.getClosest(event.point, p);
if (highLightP) {
highlightPoint(highLightP, p);
start = highLightP;
}
};

sketchTool.onMouseDown = function (event) {
start = placeMouthDown(event, start, p, pathId);
};

sketchTool.onMouseDrag = function (event) {
var snap = Inv.getClosest(event.point, p);
end = snap ? snap : event.point;

placeArrow(start, end, p, sketchValue);

if (sketchValue.drawArc) placeArc(start, end, p, sketchValue);

if (sketchValue.drawLengthLabel)
placeLengthSymbol(start, end, p, sketchValue);
};

sketchTool.onMouseUp = function (event) {
var snap = Inv.getClosest(event.point, p);
end = snap ? snap : event.point;
placeMouthUp(start, end, p, pathId);
};
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more