我有一个working OOP code递归地将一个图形元素组合呈现给画布。我有点不喜欢它,我试图看看功能版本会是什么样子。
当然,可以编写一个专门的递归纯函数,但由于框架涉及类似的算法,我想:
受到Lazy.js的启发,我已经开始编码并且做到了这一点:
LazyTree.from( drawing )
.keepNodes( visible )
.keepChildrenOf( nonClipping )
.traverse( log );
但至于地图和弃用 - 我有很多未解答的问题。
这是我试图解决的问题的简化版本:
矩形的合成(层次结构)。每个边界都在相对坐标(到它的父节点):
const drawing = {
name: 'Face',
bounds: { x: 10, y: 10, w: 100, h: 100 },
children: [{
name: 'Left eye',
bounds: { x: 10, y: 10, w: 20, h: 20 }, // Abs: (20, 20, 20, 20)
children: [{
name: 'Left pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (25, 25, 10, 10)
}]
},{
name: 'Right eye',
bounds: { x: 70, y: 10, w: 20, h: 20 }, // Abs: (80, 20, 20, 20)
children: [{
name: 'Right pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (85, 25, 10, 10)
}]
}]
};
任务是将此合成转换为具有绝对坐标的合成(如注释中所示)。
子的绝对坐标是其父绝对坐标转换的相对坐标。所以带有累加器的 fold 是候选者。
但折叠与 catamorphism 和动词如 combine 相关联,通常会返回单个值。
有问题的转换采用树并返回相同的结构但具有不同的值 - 因此它听起来更像是地图,但需要累加器。< / p>
就累加器而言,值得注意的是特定节点的所有子节点都应该获得相同的累加器。对于上面的数据,Left eye
和Right eye
都应获得相同的Face
绝对坐标(而不是Right eye
获取Left eye
的返回累加器深度优先遍历)。
我不清楚的另一件事是谁应该负责构建输出树。它应该是高阶函数(折叠,映射还是其他),还是它应该是聚合器?
与上一节相关,考虑剪切其子节点的所有矩形,以及以下组成:
const drawing = {
name: 'Parent',
bounds: { x: 10, y: 10, w: 10, h: 10 },
children: [{
name: 'Child',
bounds: { x: 1000000, y: 1000000, w: 10, h: 10 },
children: [{
name: 'Grandchild',
bounds: { x: 5, y: 5, w: 5, h: 5 }
}]
}]
};
Child
边界与其父节点(Parent
)的关系超出范围,因此当遍历到Child
时,分支遍历应该停止(没有点遍历{{1} }})。
问题是:如何通过折叠功能实现这一点?一种解决方案是在累加器返回约定的值(例如Grandchild
)时停止分支遍历。但这与列表的折叠API有所不同。
渲染算法包括:
undefined
我想知道如何通过fill( shape );
renderChildren( shape );
stroke( shape );
或traverse()
来实现这一目标。这些应该采取2次回调(前,后)吗?
树遍历可能是:
使用列表,我们有each()
之类的功能。 Lazy.js允许adding a custom iterator然后可以链接。
因此,处理遍历策略的FP方式似乎是一种转换功能。还有什么吗?
我已经谈到了使用数据管道模型为树结构实现渲染算法的一些挑战。
我怀疑其他FP方法是否更合适?也许数据管道模型不适合这类问题。或许,我应该忘记在FP库中看到的API(几乎只处理列表)并创建一个适合于手头任务的API(例如,具有也涉及累加器的map函数)。
我找不到专门用于树的FP库,而且那里的信息通常仅限于非常简单的问题。
所以希望有人会回答“这就是应该如何做的事情”。
答案 0 :(得分:0)
据我所知,您可以按照以下方式了解详情。
它将继续遍历父级边界内剩余的项目,将其坐标转换为绝对值,然后再将其渲染。但是,如果孩子的边界与父母的边界重叠,则跳过孩子及其后代。没有转换为绝对坐标和渲染。
function render(bounds){
console.log("Rendered:", bounds);
}
function relToAbs(o, b = {x: 0, y:0, w:Infinity, h:Infinity}, go = true){
go = o.bounds.x < b.w && o.bounds.y < b.h ? (o.bounds.x += b.x, o.bounds.y += b.y, render(o.bounds), go) : !go;
o.children && go && (o.children = o.children.map(p => relToAbs(p,o.bounds,go)));
return o;
}
var drawing = { name: 'Face',
bounds: { x: 10, y: 10, w: 100, h: 100 },
children: [{ name: 'Left eye',
bounds: { x: 200, y: 10, w: 20, h: 20 }, // Abs: (20, 20, 20, 20)
children: [{ name: 'Left pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (25, 25, 10, 10)
}]
},
{ name: 'Right eye',
bounds: { x: 70, y: 10, w: 20, h: 20 }, // Abs: (80, 20, 20, 20)
children: [{ name: 'Right pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (85, 25, 10, 10)
}]
}]
};
console.log(JSON.stringify(relToAbs(drawing),null,2));