mirror of
https://gitee.com/coder-xiaomo/algorithm-visualization
synced 2025-09-10 14:31:40 +08:00
import webpack
This commit is contained in:
364
src/assets/js/class.js
Normal file
364
src/assets/js/class.js
Normal file
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* 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({
|
||||
onComplete: function () {
|
||||
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"])
|
||||
|
||||
// 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 = {
|
||||
duration: 0.2,
|
||||
ease: "sine.inOut",
|
||||
delay: 0,
|
||||
yoyo: true,
|
||||
}
|
||||
|
||||
// 如果是相邻的两个元素交换,或者要交换的两个元素是同一个元素
|
||||
if (Math.abs(fromIndex - toIndex) <= 1) {
|
||||
deltaY /= 2
|
||||
animateSettings.duration = 0.25
|
||||
}
|
||||
|
||||
var that = this
|
||||
let timeline = gsap.timeline({
|
||||
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)
|
||||
updateConsole("当前数组为:" + customAttr.nodes.toString().replaceAll(",", ", "))
|
||||
|
||||
console.log("done")
|
||||
}
|
||||
}).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) {
|
||||
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 = {
|
||||
duration: 0.2,
|
||||
ease: "sine.inOut",
|
||||
delay: 0,
|
||||
yoyo: true,
|
||||
}
|
||||
|
||||
let timeline = gsap.timeline({
|
||||
onComplete: function () {
|
||||
console.log("hightlight done")
|
||||
}
|
||||
}).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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user