1
0
mirror of https://gitee.com/coder-xiaomo/algorithm-visualization synced 2025-09-06 21:01:39 +08:00
Code Issues Projects Releases Wiki Activity GitHub Gitee
Files
algorithm-visualization/src/assets/js/class.js

467 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* WorkSpace class v0.1.0
*
* @author coder-xiaomo
* @date 2022-05-15
*
* Released under the MIT license
*/
class WorkSpace {
static settings = null;
static primaryCanvas = null;
constructor(settings) {
this.settings = settings
// 清除原有内容
settings.container
.style("width", settings.width)
.style("height", settings.height)
// .attr("width", settings.width)
// .attr("height", settings.height)
.html("")
// 创建工作区SVG
this.primaryCanvas = settings.container.append("svg")
// 添加id
.attr("id", "primaryCanvas")
// 设置 SVG 宽高
.attr("width", "100%")
.attr("height", "100%")
// 背景色
.style("background-color", settings.colorMap["background"])
}
}
/**
* Shape class v0.1.0
*
* @author coder-xiaomo
* @date 2022-05-15
*
* Released under the MIT license
*/
class Shape {
static workSpace = null;
constructor(workSpace) {
this.workSpace = workSpace
}
addShape(shape, id) {
var settings = this.workSpace.settings
return workSpace.primaryCanvas.append(shape)
.style("transform", `translate(${settings.margin.left}px, ${settings.margin.top}px)`)
.attr("id", id)
}
addShape_NoTransform(shape, id) {
return workSpace.primaryCanvas.append(shape)
.attr("id", id)
}
// 添加矩形
rectangle(id, { x, y, width, height, fillColor = "white", strokeColor = "black" }) {
return this.addShape("rect", id)
.attr("x", x)
.attr("y", y)
.attr("width", width)
.attr("height", height)
.style("fill", fillColor)
.style("stroke", strokeColor)
}
// 添加圆形
circle(id, { x, y, radius, fillColor = "white", strokeColor = "black" }) {
return this.addShape("circle", id)
.attr("cx", x)
.attr("cy", y)
.attr("r", radius)
.style("fill", fillColor)
.style("stroke", strokeColor)
}
// 添加文本
text(id, { x, y, text, fillColor }) {
return this.addShape("text", id)
.attr("x", x)
.attr("y", y)
.text(text)
.style("fill", fillColor)
}
// 添加线
line(id, { x1, y1, x2, y2, strokeColor }) {
return this.addShape("line", id)
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.style("stroke", strokeColor)
}
// 添加路径
path(id, d, { fillColor = "white", strokeColor = "black" }) {
return this.addShape("path", id)
.attr("d", d)
.style("fill", fillColor)
.style("stroke", strokeColor)
}
// 添加坐标轴
axis(id, { transform, axis }) {
return this.addShape_NoTransform("g", id)
.attr("transform", transform)
.call(axis)
}
// 添加一个链表节点
addNode(id, x, y, width, height, text) {
var primaryCanvas = workSpace.primaryCanvas
primaryCanvas.append("rect", id)
.attr("x", x)
.attr("y", y)
.attr("width", width)
.attr("height", height)
.style("fill", workSpace.settings.colorMap["fill"])
.style("stroke", workSpace.settings.colorMap["stroke"])
primaryCanvas.append("text", id + "_text")
.attr("x", x)
.attr("y", y)
// .attr("x", x + width / 2)
// .attr("y", y + height / 2)
.text(text)
.style("fill", workSpace.settings.colorMap["text"])
}
// 绘制一个链表
getLinkedListFragment(id, nodes) {
var settings = this.workSpace.settings
let displayMaxWidth = 1 * settings.innerSize.width
let displayMaxHeight = 1 * settings.innerSize.height
let areaWidth = 1 * nodes.length // 按照1个Unit来计算
let areaHeight = 1 // 按照1个Unit来计算
let oneUnit = 100
// 可以假设高度相等比较宽度,这样好理解
if (displayMaxWidth / displayMaxHeight > areaWidth / areaHeight) {
// 展示区域左右有多余空间
oneUnit = displayMaxHeight / areaHeight
} else {
// 展示区域上下有空间(或刚刚好)
oneUnit = displayMaxWidth / areaWidth
}
// 定义最大值
if (oneUnit > 120) oneUnit = 120
let fragment = document.createDocumentFragment()
fragment.customAttr = {
id: id,
nodes: nodes,
type: "linkedList",
oneUnit: oneUnit,
gsapTimeline: gsap.timeline({
onStart: function () {
consoleClear()
},
onComplete: function () {
consoleLog(`排序完成`)
console.log("all done")
// this.seek(0)
}
})
}
// console.log(fragment.customAttr)
// <g></g> 元素不能设置 width 和 height
let g = d3.select(fragment)
.append("svg:g")
.attr("id", id)
.attr("fill", "white")
.attr("transform", `translate(${settings.margin.left}, ${settings.margin.top})`)
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let _g = g.append("svg:g")
_g.append("svg:rect")
.attr("x", i * oneUnit)
.attr("y", 0)
.attr("width", oneUnit)
.attr("height", oneUnit)
.attr("fill", settings.colorMap["fill"])
.attr("stroke", settings.colorMap["stroke"])
_g.append("svg:text")
.text(node)
.attr("x", i * oneUnit + oneUnit / 2)
.attr("y", oneUnit / 2)
.attr("width", oneUnit)
.attr("height", oneUnit)
.attr("fill", settings.colorMap["text"])
// 调试用
if (settings.debugMode)
_g.append("svg:text")
.text(i)
.attr("x", i * oneUnit)
.attr("y", oneUnit)
.attr("width", oneUnit)
.attr("height", oneUnit)
.attr("fill", "black")
// console.log(text.node().getBBox())
// console.log(text.node().getBoundingClientRect())
}
// g.append("svg:rect")
// .attr("width", oneUnit * nodes.length)
// .attr("height", oneUnit)
// .style("fill", "none")
// .style("stroke", "green")
return fragment
}
}
class VectorAnimation {
constructor(workSpace) {
this.workSpace = workSpace
}
swapElementAttr(attrName/* or attrNameList */, element1, element2) {
function exchange(attrName) {
// 保存 element1 的属性,将 element2 的属性赋值给 element1, 再将保存的属性赋值给 element2
var tmp = element1.getAttribute(attrName)
element1.setAttribute(attrName, element2.getAttribute(attrName))
element2.setAttribute(attrName, tmp)
}
if (typeof attrName === "string") {
exchange(attrName)
} else if (typeof attrName === "object") {
for (let i = 0; i < attrName.length; i++) {
exchange(attrName[i])
}
}
}
swapElementInnerHTML(element1, element2) {
var tmp = element1.innerHTML
element1.innerHTML = element2.innerHTML
element2.innerHTML = tmp
}
swapElementsAttr(elementPairList) {
// [
// [attrName or attrNameList, element1, element2],
// [attrName or attrNameList, element1, element2],
// ...
// ]
for (let i = 0; i < elementPairList.length; i++) {
let elementPair = elementPairList[i]
this.swapElementAttr(elementPair[0], elementPair[1], elementPair[2])
}
}
// 交换数组元素
swapLinkedListItems(id, [fromIndex, toIndex]) {
if (fromIndex < toIndex)
[fromIndex, toIndex] = [toIndex, fromIndex]
var settings = this.workSpace.settings
let linkedList = document.getElementById(id)
let customAttr = linkedList.customAttr
// console.log(customAttr)
var gList = linkedList.childNodes
let from = gList[fromIndex]
let to = gList[toIndex]
var deltaX = customAttr.oneUnit * (fromIndex - toIndex);
var deltaY = customAttr.oneUnit * 1.08
var animateSettings = this.workSpace.settings.animation.getConf()
// 如果是相邻的两个元素交换,或者要交换的两个元素是同一个元素
if (Math.abs(fromIndex - toIndex) <= 1) {
deltaY /= 2
animateSettings.duration *= 0.6
}
var that = this
let timeline = gsap.timeline({
onStart: function () {
displayCurrentArray(customAttr.nodes)
consoleLog(`交换索引为 ${fromIndex}${toIndex} 的两个元素`)
},
onComplete: function () {
// 交换DOM元素中的值
that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1])
// 交换 node 列表中的值
// console.log(customAttr.nodes)
let tmp = customAttr.nodes[fromIndex]
customAttr.nodes[fromIndex] = customAttr.nodes[toIndex]
customAttr.nodes[toIndex] = tmp
// console.log(customAttr.nodes)
displayCurrentArray(customAttr.nodes)
console.log("animation done (swap)")
}
}).add([
gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }),
gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }),
gsap.to(from.childNodes, { ...animateSettings, y: -deltaY }),
gsap.to(to.childNodes, { ...animateSettings, y: deltaY }),
]).add([
gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }),
gsap.to(to.childNodes, { ...animateSettings, x: deltaX }),
]).add([
gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }),
gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }),
gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }),
gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }),
gsap.to(from.childNodes, { ...animateSettings, y: 0 }),
gsap.to(to.childNodes, { ...animateSettings, y: 0 }),
]).add([
// 恢复到动画之前的状态
gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }),
gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }),
])
customAttr.gsapTimeline.add(timeline)
}
// 高亮数组元素
highlightLinkedListItems(id, indexList, onStartCallback) {
let linkedList = document.getElementById(id)
let customAttr = linkedList.customAttr
var gList = linkedList.childNodes
if (typeof (indexList) === "number")
indexList = [indexList]
var hightlightElementList_fill = []
var hightlightElementList_text = []
for (let i = 0; i < indexList.length; i++) {
const index = indexList[i];
hightlightElementList_fill.push(gList[index].childNodes[0])
hightlightElementList_text.push(gList[index].childNodes[1])
}
var animateSettings = this.workSpace.settings.animation.getConf()
let timeline = gsap.timeline({
onStart: function () {
displayCurrentArray(customAttr.nodes)
if (onStartCallback)
onStartCallback()
},
onComplete: function () {
console.log("animation done (hightlight)")
}
}).add([
gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text_focus"] }),
]).add([
gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill"] }),
gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text"] }),
])
customAttr.gsapTimeline.add(timeline)
}
// 比较数组元素
compareLinkedListItems(id, index1, index2) {
this.highlightLinkedListItems(id, [index1, index2], function () {
consoleLog(`比较索引为 ${index1}${index2} 的两个元素`)
})
}
// 弹出/弹回 数组元素
popupLinkedListItems(id, index, { popBack = false }) {
let linkedList = document.getElementById(id)
let customAttr = linkedList.customAttr
var gList = linkedList.childNodes
var deltaY = customAttr.oneUnit * 1.08
if (popBack) deltaY = 0
let timeline = gsap.timeline({
onStart: function () {
if (typeof (onStartCallback) === "function")
onStartCallback()
},
onComplete: function () {
console.log("animation done (popup)")
}
}).add([
gsap.to(gList[index], { ...this.workSpace.settings.animation.getConf(), y: deltaY }),
])
customAttr.gsapTimeline.add(timeline)
}
// 交换相邻数组元素位置(仅在水平方向呼唤,垂直方向不做调整) 【fromIndex 是y方向上突出的元素】
exchangeLinkedListItems(id, fromIndex, toIndex) {
if (fromIndex < toIndex)
[fromIndex, toIndex] = [toIndex, fromIndex]
var settings = this.workSpace.settings
let linkedList = document.getElementById(id)
let customAttr = linkedList.customAttr
// console.log(customAttr)
var gList = linkedList.childNodes
let from = gList[fromIndex]
let to = gList[toIndex]
var deltaX = customAttr.oneUnit * (fromIndex - toIndex);
var deltaY = customAttr.oneUnit * 1.08 // 要跟 popupLinkedListItems 函数中的 deltaY 完全一致
var animateSettings = settings.animation.getConf()
var that = this
let timeline = gsap.timeline({
onStart: function () {
displayCurrentArray(customAttr.nodes)
consoleLog(`交换索引为 ${fromIndex}${toIndex} 的两个元素`)
},
onComplete: function () {
// 交换DOM元素中的值
that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1])
// 交换 node 列表中的值
// console.log(customAttr.nodes)
let tmp = customAttr.nodes[fromIndex]
customAttr.nodes[fromIndex] = customAttr.nodes[toIndex]
customAttr.nodes[toIndex] = tmp
// console.log(customAttr.nodes)
displayCurrentArray(customAttr.nodes)
console.log("animation done (exchange)")
}
}).add([
gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }),
gsap.to(to.childNodes, { ...animateSettings, x: deltaX }),
]).add([
// 恢复到动画之前的状态
gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0 }),
gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0 }),
// 由于两个元素一个突出一个不突出所以还要交换两个y
gsap.to(from, { ...animateSettings, duration: 0, y: 0 }),
gsap.to(to, { ...animateSettings, duration: 0, y: deltaY }),
])
customAttr.gsapTimeline.add(timeline)
}
}