Related
I'm having an issue with Canvas when trying to create a generated collection of .png images.
I can create the .png files fine but the first image or the image before is not being cleared.
index.js
const fs = require("fs");
const { createCanvas, loadImage } = require("canvas");
const canvas = createCanvas(1000,1000);
const ctx = canvas.getContext("2d");
const edition = 10;
const {layers,width,height} = require("./input/config.js");
const saveLayer = (_canvas, _edition) => {
fs.writeFileSync(`./output/${_edition}.png`, _canvas.toBuffer("image/png"));
};
const drawLayer = async (_layer, _edition) => {
let element = _layer.elements[Math.floor(Math.random() * _layer.elements.length)];
const image = await loadImage(`${_layer.location}${element.fileName}`);
ctx.drawImage(
image,
_layer.position.x,
_layer.position.y,
_layer.size.width,
_layer.size.height
);
console.log(`I created the ${_layer.name} layer, and chose element ${element.name}`);
saveLayer(canvas, _edition);
};
for(let i = 0; i <= edition; i++){
layers.forEach(layer => {
drawLayer(layer, i);
});
console.log("Creating edition " + i);
};
config.js
const fs = require("fs");
const width = 1000;
const height = 1000;
const dir = __dirname;
const rarity = [
{key: "", val: "original" },
{key: "_r", val: "rare" },
{key: "_sr", val: "super rare" },
];
const addRarity = (_str) => {
let itemRarity;
rarity.forEach((r) => {
if (_str.includes(r.key)){
itemRarity = r.val;
}
});
return itemRarity;
};
const cleanName = (_str) => {
let name = _str.slice(0, -4);
return name;
};
const getElements = (path) => {
return fs
.readdirSync(path)
//.filter((item) => !/(^|\/)\.|^\/\.|/g.test(item))
.map((i,index) => {
return {
id: index,
name: cleanName(i),
fileName: i,
rarity: addRarity(i),
};
});
};
const layers = [
{
id: 1,
name: "0",
location: `${dir}/0/`,
elements: getElements(`${dir}/0/`),
position: {x:0, y:0},
size: {width: width, height: height},
},
{
id: 2,
name: "1",
location: `${dir}/1/`,
elements: getElements(`${dir}/1/`),
position: {x:0, y:0},
size: {width: width, height: height},
},
{
id: 3,
name: "2",
location: `${dir}/2/`,
elements: getElements(`${dir}/2/`),
position: {x:0, y:0},
size: {width: width, height: height},
},
{
id: 4,
name: "3",
location: `${dir}/3/`,
elements: getElements(`${dir}/3/`),
position: {x:0, y:0},
size: {width: width, height: height},
},
{
id: 5,
name: "4",
location: `${dir}/4/`,
elements: getElements(`${dir}/4/`),
position: {x:0, y:0},
size: {width: width, height: height},
},
];
//console.log(layers);
module.exports = {layers,width,height};
The code is working fine and I'm able to create 10 .png files.
From the images above you can see the blue hat from Image 1 is still showing in Image 2.
Edit: What I've tried -
ctx.beginPath();
context.clearRect(0, 0, canvas.width, canvas.height);
const createNFTs = async() =>{
for(let i = 1; i <= edition; i++){
for (const layer of layers) await drawLayer(layer, i);
console.log("Creating edition " + i);
};
};
Final Code:
for(let i = 1; i <= edition; i++){
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const layer of layers) await drawLayer(layer, i);
console.log("Creating edition " + i);
};
You have a forEach calling an async function.
forEach and async do not go together: the forEach is drawing the next image before the previous one is finished
Replace
layers.forEach(layer => {
drawLayer(layer, i);
});
with
for (const layer of layers) {
await drawLayer(layer, i);
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
(adding clear)
and put it in an async function
I am developing a pdf viewer, with the ability to apply additional elements by the user.
Pdf has drag and drop capability, zoom in and out.
I ran into the following problems that baffle me:
Correctly position the new elements so that the image is set at coordinates x: 0, y: 0. (image 1) When displacing pdf, I can not track what distance to displace the coordinates of the new element. (image 2)
To keep the image quality when zooming in, I set the camera Zoom (init scale) to 3. I can't find a solution how to display it in a smaller size when opening the page, so as not to lose quality when zooming in. (image 3)
import React, { useEffect, useState, useRef } from 'react';
import * as PDFJS from 'pdfjs-dist'
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
import {makeStyles} from "#material-ui/core/styles";
export const PdfCanvas = ({pageNum, editCanvas, colorCircle}) => {
const canvasRef = useRef();
const [image, setImage] = useState();
const fileUri = "https://s3-us-east-2.amazonaws.com/c9e8e9c8-7ec0-412f-81cc-60fc07201419/Bar%20Yohai_Coordination%20plane%2000.pdf";
const mainCanvas = canvasRef.current
const mainCtx = mainCanvas?.getContext('2d');
let cameraOffset = { x: window.innerWidth/2, y: window.innerHeight/2 }
let cameraZoom = 3
let MAX_ZOOM = 108
let MIN_ZOOM = 0.01
let SCROLL_SENSITIVITY = 0.0005
const [positionArr, setPositionArr] = useState([
{id: '1', x: 400, y: 40, radius: 10, color: 'rgb(255,0,0)'},
{id: '2', x: 800, y: 40, radius: 10, color: 'rgb(134,211,17)'},
{id: '3', x: 100, y: 40, radius: 10, color: 'rgb(32,52,157)'},
{id: '4', x: 720, y: 40, radius: 10, color: 'rgb(10,9,9)'},
{id: '5', x: 640, y: 40, radius: 10, color: 'rgb(227,170,24)'},
]);
function isIntersect(point: { x: any; y: any; }, circle: { id?: string; x: any; y: any; radius: any; color?: string; }) {
return Math.sqrt((point.x-circle.x) ** 2 + (point.y - circle.y) ** 2) < circle.radius;
}
const renderPage = (pageNum) => {
const canvas = document.createElement('canvas'), ctx = canvas.getContext('2d');
const container = document.getElementById("container")
if (fileUri) {
const loadingTask = PDFJS.getDocument(fileUri);
loadingTask.promise.then(loadedPdf => {
loadedPdf && loadedPdf.getPage(pageNum).then(function(page) {
const viewport = page.getViewport({scale: cameraZoom});
canvas.width = viewport.width;
canvas.height = viewport.height ;
canvas.style.width = "100%";
canvas.style.height = "100%";
container.style.width = Math.floor(viewport.width/cameraZoom) + 'pt';
container.style.height = Math.floor(viewport.height/cameraZoom) + 'pt';
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
page.render(renderContext).promise.then(() => {
var pdfImage = new Image();
pdfImage.src = canvas.toDataURL("image/png", 1)
setImage(pdfImage)
})
});
}, function (reason) {
console.error(reason);
});
}
};
function draw()
{
if (mainCanvas) {
mainCanvas.width = visualViewport.width
mainCanvas.height = visualViewport.height
}
// Translate to the canvas centre before zooming - so you'll always zoom on what you're looking directly at
if (mainCtx) {
mainCtx.scale(cameraZoom, cameraZoom)
mainCtx.translate( -window.innerWidth / .8 + cameraOffset.x, -window.innerHeight / .8 + cameraOffset.y )
mainCtx.clearRect(0,0, window.innerWidth, window.innerHeight)
mainCtx.fillStyle = "#991111"
if (image) {
mainCtx.drawImage(image, 1, 1);
(positionArr || []).map(circle => {
mainCtx?.beginPath();
mainCtx?.arc(circle.x, circle.y, circle.radius / cameraZoom, 0, 2 * Math.PI, false);
if (mainCtx) {
mainCtx.fillStyle = circle.color
}
mainCtx?.fill();
});
}
}
requestAnimationFrame( draw )
}
// Gets the relevant location from a mouse or single touch event
function getEventLocation(e)
{
if (e.touches && e.touches.length === 1)
{
return { x:e.touches[0].clientX, y: e.touches[0].clientY }
}
else if (e.clientX && e.clientY)
{
return { x: e.clientX, y: e.clientY }
}
}
let isDragging = false
let dragStart = { x: 0, y: 0 }
function onPointerDown(e)
{
isDragging = true
dragStart.x = getEventLocation(e).x/cameraZoom - cameraOffset.x
dragStart.y = getEventLocation(e).y/cameraZoom - cameraOffset.y
}
function onPointerUp(e)
{
isDragging = false
initialPinchDistance = null
lastZoom = cameraZoom
}
function onPointerMove(e)
{
if (isDragging)
{
cameraOffset.x = getEventLocation(e).x/cameraZoom - dragStart.x
cameraOffset.y = getEventLocation(e).y/cameraZoom - dragStart.y
}
}
function handleTouch(e, singleTouchHandler)
{
if ( e.touches.length === 1 )
{
singleTouchHandler(e)
}
else if (e.type === "touchmove" && e.touches.length === 2)
{
isDragging = false
handlePinch(e)
}
}
let initialPinchDistance = null
let lastZoom = cameraZoom
function handlePinch(e)
{
e.preventDefault()
let touch1 = { x: e.touches[0].clientX, y: e.touches[0].clientY }
let touch2 = { x: e.touches[1].clientX, y: e.touches[1].clientY }
// This is distance squared, but no need for an expensive sqrt as it's only used in ratio
let currentDistance = (touch1.x - touch2.x)**2 + (touch1.y - touch2.y)**2
if (initialPinchDistance == null)
{
initialPinchDistance = currentDistance
}
else
{
adjustZoom( null, currentDistance/initialPinchDistance )
}
}
function adjustZoom(zoomAmount, zoomFactor, e)
{
if (!isDragging)
{
if (zoomAmount)
{
cameraZoom += zoomAmount*zoomFactor
}
else if (zoomFactor)
{
cameraZoom = zoomFactor*lastZoom
}
cameraZoom = Math.min( cameraZoom, MAX_ZOOM )
cameraZoom = Math.max( cameraZoom, MIN_ZOOM )
}
}
const handleAddIcon = (event: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
if (editCanvas){
const x = event.nativeEvent.offsetX / cameraZoom
const y = event.nativeEvent.offsetY / cameraZoom
console.log(x, y)
const circle = {
id: Math.random().toFixed(2), x, y, radius: 10, color: colorCircle
}
mainCtx?.beginPath();
mainCtx?.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);
if (mainCtx) {
mainCtx.fillStyle = circle.color
}
mainCtx?.fill();
const newArr = positionArr;
newArr.push(circle)
setPositionArr(newArr)
} else {
const point = {
x : event.nativeEvent.offsetX / cameraZoom,
y : event.nativeEvent.offsetY / cameraZoom
};
console.log(cameraOffset, "cameraOffset")
console.log(point, "point")
positionArr.forEach(circle => {
if (isIntersect(point, circle)) {
alert('click on circle: ' + circle.id);
}
});
}
};
// Ready, set, go
useEffect(() => {
renderPage(pageNum)
}, [])
draw()
return (
<div id="container">
<canvas
onWheel={(e) => adjustZoom(cameraZoom, e.deltaY*SCROLL_SENSITIVITY, e)}
onMouseMove={onPointerMove}
onMouseUp={onPointerUp}
onTouchStart={(e) => handleTouch(e, onPointerDown)}
onTouchMove={(e) => handleTouch(e, onPointerMove)}
onTouchEnd={(e) => handleTouch(e, onPointerUp)}
onMouseDown={(e) => {onPointerDown(e)}}
onClick={(e) => handleAddIcon(e)}
ref={canvasRef} />
</div>
)
}
image 1
image 2
image 3
I would be very glad if someone can tell me the ways to solve these problems.
I am learning the concepts of Composition in JS. Below is my demo code.
The moveBy function assigns the values correctly to x and y.
However, the setFillColor function does not assign the passed value to fillColor.
What exactly is happening when the setFillColor function is called?
const withMoveBy = (shape) => ({
moveBy: (diffX, diffY) => {
shape.dimensions.x += diffX;
shape.dimensions.y += diffY;
},
});
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
console.log(shape.fillColor); // 1
shape.fillColor = color;
shape.dimensions.fillColor = color;
console.log(shape.fillColor); // 2
},
});
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
shape = {
...shape,
...withSetFillColor(shape),
...withMoveBy(shape),
};
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
OUTPUT:
Line marked as // 1 prints white in case of rectangle as well as circle.
Line marked as // 2 prints red for rectangle and blue for circle.
The final output is:
{
"type": "rectangle",
"fillColor": "white",
"dimensions": {
"x": 3,
"y": 4,
"width": 10,
"height": 10,
"fillColor": "red"
}
}
{
"type": "circle",
"fillColor": "white",
"dimensions": {
"x": 11,
"y": 12,
"diameter": 10,
"fillColor": "blue"
}
}
The fillColor as the property of object is still white.
However, the one inside of dimensions has taken the correct value.
The problem stems from this assignment in createShape - annotations by me:
// creating the "new object"
shape = {
...shape, // shallow copying of the "old object"
...withSetFillColor(shape),
...withMoveBy(shape),
};
Here, you create a new object that is composed of:
shallow-copied properties of the existing ...shape (type, fillcolor, dimensions which is an object)
setFillColor, a closure that is bound to shape (the old object)
moveBy, a closure that is bound to shape (the old object)
After this statement is executed, you have created two shapes:
The "old object", which the methods operate on
The "new object", which you return
Out of the properties copied from the old object, only dimensions is a non-primitive value, so it is shared between the instances.
Then, when you call:
r.moveBy(2, 3);
it changes oldShape.dimensions, but it's the same object as newShape.dimensions, so it's visible in the output.
However, this call:
r.setFillColor('red');
modifies the fillColor property of the oldShape, which you are not seeing. It also writes to oldShape.dimensions.fillColor, which, again, is shared between the objects, so that change is visible in both.
Let me illustrate the problem by re-writing your code. I have removed some of the details to focus on the issue only. Added annotations and logging to the code to show more clearly what happens:
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
console.log(`now changing shape with id [${shape.id}]`);
shape.fillColor = color;
shape.dimensions.fillColor = color;
},
});
const shapeRectangle = (dimensions) => ({
id: 1, //add an ID of the created object for illustrative purpose
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
//variable is now named 1 to showcase what happens
let shape1 = null;
switch (type) {
case 'rectangle': {
shape1 = shapeRectangle(dimensions);
break;
}
}
//this is effectively what happens when you clone and reassign an object:
//a *second one* is created but the first one persists
let shape2 = null;
if (shape1) {
shape2 = {
...shape1,
...withSetFillColor(shape1),
id: 2, //make it a different ID for illustrative purpose
};
}
console.log(`Created shape1 and shape2 and they are the same: ${shape1 === shape2}`);
console.log(`The dimensions object is the same: ${shape1.dimensions === shape2.dimensions}`);
return shape2;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
r.setFillColor('red');
console.log(r);
You create and manipulate two different objects. This is the reason why the code assigns a property to the object but it appears as if it is not changed.
There are several way to deal with this.
Only create one object and assign to it
If you use Object.assign() you can directly change one object instead of having two competing ones. Thus, passing the object to the withX() functions will work as intended.
const withMoveBy = (shape) => ({
moveBy: (diffX, diffY) => {
shape.dimensions.x += diffX;
shape.dimensions.y += diffY;
},
});
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
shape.fillColor = color;
shape.dimensions.fillColor = color;
},
});
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
//use Object assign to only manipulate one `shape` object
Object.assign(
shape,
withSetFillColor(shape),
withMoveBy(shape)
);
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
Don't use arrow functions, use this instead
Alternatively, use regular functions or the shorthand method definition syntax which lets you use this. You can then add these methods to your object and use this to refer to the object, instead of having to pass it in.
const withMoveBy = { //no need for a function to produce the object
moveBy(diffX, diffY) { //shorthand method syntax
this.dimensions.x += diffX;
this.dimensions.y += diffY;
},
};
const withSetFillColor = { //no need for a function to produce the object
setFillColor(color) { //shorthand method syntax
this.fillColor = color;
this.dimensions.fillColor = color;
},
};
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
shape = {
...shape,
...withSetFillColor,
...withMoveBy,
};
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
A mixed approach
This is more of an explanation of what's happening than an actual new approach.
Both of the above both work but show two sides of the same coin. Combining objects together is called mixin*. Mixins are similar to object composition because you build up more complex objects from simpler ones but also a separate category of its own since you do it via concatenation.
Traditionally, you would use Object.assign(obj, mixinA, mixinB) for adding things to obj. Which makes it similar to the first approach. However, mixinA and mixinB would be actual objects like in the second approach.
Using class syntax, there is an interesting alternative to add mixins to a class. I'm adding it here just to show it - it's totally OK to not use classes and use regular objects instead.
const withMoveBy = Base => class extends Base { //mixin
moveBy(diffX, diffY) {
this.dimensions.x += diffX;
this.dimensions.y += diffY;
}
};
const withSetFillColor = Base => class extends Base { //mixin
setFillColor(color) {
this.fillColor = color;
this.dimensions.fillColor = color;
}
};
class Shape {
constructor({type, fillColor, dimensions}) {
this.type = type;
this.fillColor = fillColor;
this.dimensions = dimensions;
}
}
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shapeArgs = null;
switch (type) {
case 'rectangle': {
shapeArgs = shapeRectangle(dimensions);
break;
}
case 'circle': {
shapeArgs = shapeCircle(dimensions);
break;
}
}
let shape = null;
if (shapeArgs) {
//add mixins to the Shape class
const mixedInConstructor = withMoveBy(withSetFillColor(Shape));
//create the enhanced class
shape = new mixedInConstructor(shapeArgs);
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
* Yes, the title was a pun. You can laugh now.
I have constructed a tree graph using html5, css3 but the nodes are static. Now I wanna make that graph dynamic. Here dynamic means suppose the node count increases and there are multiple children, now the graph will be generated with the new nodes and children.
This is just an example using vanilla JavaScript. I've taken a lot of inspiration from d3, including how to store the data as a self-referencing tree of nodes and how to traverse the tree breadth-first.
I've tried to comment the code as well as possible, I hope this gives you some inspiration. I'd try to improve the positioning of the labels a bit, and/or increase the margins so they're better visible.
const data = [{
id: 0,
label: "Command Sequence Starting",
},
{
id: 1,
parents: [0],
label: "W_SCMadl_refresh",
status: "done",
},
{
id: 2,
parents: [1],
label: "W_adl_photo",
status: "done",
},
{
id: 3,
parents: [2],
label: "W_adl_collect",
status: "done",
},
{
id: 4,
parents: [3],
label: "W_adl_collect_cr",
status: "done",
},
{
id: 5,
parents: [4],
label: "W_adl_sync",
status: "aborted",
},
{
id: 6,
parents: [5],
label: "W_adl_attach",
status: "waiting",
},
{
id: 7,
parents: [6],
label: "W_adl_attach",
status: "waiting",
},
{
id: 8,
parents: [7],
label: "W_adl_publish",
status: "waiting",
},
{
id: 9,
parents: [8],
label: "W_adl_ds_ws",
status: "waiting",
},
{
id: 10,
parents: [9],
label: "W64_Shared_Preq_mkdir",
status: "waiting",
},
{
id: 11,
parents: [10, 12],
label: "W64_mkCopyPreq",
status: "waiting",
},
{
id: 12,
parents: [0],
label: "WIN64_MCCMon",
status: "done",
},
];
// Make the data array a self-referencing tree, where each node has pointers to their
// parents and their children nodes
data
.filter(d => d.parents !== undefined)
.forEach(d => {
d.parents = data.filter(p => d.parents.includes(p.id));
d.parents.forEach(p => {
if (p.children === undefined) {
p.children = [];
}
p.children.push(d);
});
});
const root = data.find(d => d.parents === undefined);
// Breadth first traversal of the tree, excuting `fn` for every node
const forEach = (root, fn) => {
const stack = [root];
while (stack.length) {
const current = stack.shift();
if (current.children) {
stack.push(...current.children);
}
fn(current);
}
};
const svg = document.querySelector(".mv-sequence svg");
const margin = {
top: 20,
bottom: 20,
right: 20,
left: 20,
};
const width = +svg.getAttribute("width") - margin.left - margin.right;
const stepHeight = 40;
const namespace = "http://www.w3.org/2000/svg";
const gContainer = document.createElementNS(namespace, "g");
gContainer.setAttribute("transform", `translate(${margin.left},${margin.top})`);
svg.appendChild(gContainer);
const linksContainer = document.createElementNS(namespace, "g");
gContainer.appendChild(linksContainer);
const nodesContainer = document.createElementNS(namespace, "g");
gContainer.appendChild(nodesContainer);
// Give node a level. First complete this loop, then start drawing, because we want to
// be robust against not all parents having a level yet
forEach(
root,
d => {
if (d === root) {
d.level = 0;
return;
}
d.level = Math.max(...d.parents.map(p => p.level)) + 1;
}
);
forEach(
root,
d => {
// Position the node based on the number of siblings.
const siblings = data.filter(n => n.level === d.level);
// If the node is an only child. The root should be in the centre,
// any other node should be in the average of it's parents
if (siblings.length === 1) {
if (d.parents === undefined) {
d.x = width / 2;
} else {
d.x = d.parents.map(p => p.x).reduce((s, v) => s + v, 0) / d.parents.length;
}
return;
}
// Otherwise, divide the space evenly for all sibling nodes
const siblingIndex = siblings.indexOf(d);
const stepWidth = width / (siblings.length - 1);
if (siblings.length % 2 === 0) {
// Even number of siblings
d.x = stepWidth * siblingIndex;
} else {
// Odd number of siblings, the center one must be in the middle
d.x = width / 2 + stepWidth * (siblingIndex - (siblings.length - 1) / 2);
}
}
);
forEach(
root,
d => {
// Append a circle and `text` for all new nodes
d.y = d.level * stepHeight;
const nodeContainer = document.createElementNS(namespace, "g");
nodeContainer.setAttribute("transform", `translate(${d.x}, ${d.y})`);
nodeContainer.classList.add("mv-command", d.status);
nodesContainer.appendChild(nodeContainer);
const circle = document.createElementNS(namespace, "circle");
circle.setAttribute("r", stepHeight / 4);
nodeContainer.appendChild(circle);
const label = document.createElementNS(namespace, "text");
label.setAttribute("dx", stepHeight / 4 + 5);
label.textContent = d.label;
nodeContainer.appendChild(label);
// Append a link from every parent to this node
(d.parents || []).forEach(p => {
const link = document.createElementNS(namespace, "path");
let path = `M${p.x} ${p.y}`;
let dx = d.x - p.x;
let dy = d.y - p.y;
if (dy > stepHeight) {
// Move down to the level of the child node
path += `v${dy - stepHeight}`;
dy = stepHeight;
}
path += `s0 ${dy / 2}, ${dx / 2} ${dy / 2}`;
path += `s${dx / 2} 0, ${dx / 2} ${dy / 2}`;
link.setAttribute("d", path)
linksContainer.appendChild(link);
})
}
);
// Finally, set the height to fit perfectly
svg.setAttribute("height", Math.max(...data.map(d => d.level)) * stepHeight + margin.top + margin.bottom);
.mv-command.done {
fill: #477738;
}
.mv-command.aborted {
fill: #844138;
}
.mv-command.waiting {
fill: #808080;
}
.mv-command.disabled {
fill: #80808080;
}
.mv-command.running {
fill: #005686;
animation: mymove 2s infinite;
}
.mv-command>text {
dominant-baseline: middle;
font-size: 12px;
}
path {
fill: none;
stroke: darkgreen;
stroke-width: 3px;
}
<div class="mv-sequence">
<svg width="200"></svg>
</div>
Could anyone please help me in creating a stepper function that would take a parameter 'n' and would increment my slider according to n's value. Circular slider I'm using is this :
https://github.com/fseehawer/react-circular-slider
This is how the knob is being set based on different angles, but what if I want to set the knob at angles separated with 20 degree angle.
const setKnobPosition = useCallback(
(radians) => {
const radius = state.radius - trackSize / 2;
const offsetRadians = radians + knobOffset[knobPosition];
const degrees = (offsetRadians > 0 ? offsetRadians : 2 * Math.PI + offsetRadians)
* (spreadDegrees / (2 * Math.PI));
const pointsInCircle = (state.angles.length - 1) / spreadDegrees;
const currentPoint = Math.round(degrees * pointsInCircle);
if (state.angles[currentPoint] !== state.currentPoint) {
onChange(state.angles[currentPoint]);
}
dispatch({
type: 'setKnobPosition',
payload: {
displayedCompassAngle: state.angles[currentPoint],
knob: {
x: radius * Math.cos(radians) + radius,
y: radius * Math.sin(radians) + radius,
},
},
});
},
[
state.radius,
state.angles,
state.displayedCompassAngle,
knobPosition,
trackSize,
direction,
onChange,
],
);
These are initial state and different props I am using.
const CircularSlider = (props) => {
const {
width = 280,
direction = 1,
knobPosition = 'top',
hideLabelValue = false,
knobDraggable = true,
trackSize = 8,
angles = [],
compassAngle = 0,
onChange = () => {},
} = props;
const initialState = {
mounted: false,
isDragging: false,
width,
radius: width / 2,
knobPosition,
displayedCompassAngle: 0,
angles,
radians: 0,
offset: 0,
knob: {
x: 0,
y: 0,
},
};