知识介绍
- 使用两个SVG进行主视图和Len视图的区分,len视图中附带额外信息
代码分析
1 使用两个SVG进行主视图和Len视图的区分
- 设置主视图
// 绘制主SVG的函数
const drawMainSVG = () => {
// 选择主SVG并设置其属性
const svgMain = d3.select(svgMainRef)
.attr("width", 1000) // 设置宽度
.attr("height", 700) // 设置高度
.style('border', '1px solid black'); // 设置边框样式
// 定义边距
const margin = { top: 20, right: 20, bottom: 30, left: 60 };
// 计算绘图区域的宽度和高度
const width = +svgMain.attr('width') - margin.left - margin.right;
const height = +svgMain.attr('height') - margin.top - margin.bottom;
// 设置X和Y轴的最小最大值
const axisXOffset = 10; // X轴偏移
const axisYOffset = 50; // Y轴偏移
const xMin = Math.min(
(d3.min(xpoints(), d => d.coords[0])) - axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) - axisXOffset
);
const xMax = Math.max(
(d3.max(xpoints(), d => d.coords[0])) + axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) + axisXOffset
);
const yMin = Math.min(
(d3.min(xpoints(), d => d.coords[1])) - axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) - axisYOffset
);
const yMax = Math.max(
(d3.max(xpoints(), d => d.coords[1])) + axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) + axisYOffset
);
// 创建X轴和Y轴的比例尺
const x = d3.scaleLinear()
.domain([xMin, xMax]) // 输入数据范围
.range([0, width]); // 输出像素范围
const y = d3.scaleLinear()
.domain([yMin, yMax]) // 输入数据范围
.range([height, 0]); // 输出像素范围
// 创建一个g元素用于绘制内容
const g = svgMain.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`); // 设置内容位置
// 绘制X坐标点的圆圈
g.selectAll('circle.jsonData')
.data(xpoints()) // 绑定数据
.enter().append('circle') // 创建圆元素
.attr('class', 'jsonData') // 设置类名
.attr('cx', d => x(d.coords[0])) // 设置圆心X坐标
.attr('cy', d => y(d.coords[1])) // 设置圆心Y坐标
.attr('r', 2) // 设置圆的半径
.attr('fill', 'steelblue'); // 设置圆的填充颜色
// 绘制Y坐标点的圆圈
g.selectAll('circle.drawingData')
.data(ypoints()) // 绑定数据
.enter().append('circle') // 创建圆元素
.attr('class', 'drawingData') // 设置类名
.attr('cx', d => x(d.coords[0])) // 设置圆心X坐标
.attr('cy', d => y(d.coords[1])) // 设置圆心Y坐标
.attr('r', 2) // 设置圆的半径
.attr('fill', 'red') // 设置圆的填充颜色
.on('mouseover', (event, d) => { // 鼠标悬停事件
const tooltip = d3.select('body') // 选择body元素创建tooltip
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute') // 绝对定位
.style('background-color', 'white') // 背景色
.style('border', '1px solid black') // 边框样式
.style('padding', '5px') // 内边距
.style('pointer-events', 'none') // 不响应鼠标事件
.style('left', `${event.pageX + 10}px`) // 设置tooltip左位置
.style('top', `${event.pageY + 10}px`) // 设置tooltip顶部位置
.text(d.text); // 显示文本内容
})
.on('mouseout', () => {
d3.select('.tooltip').remove(); // 鼠标移出时移除tooltip
});
// 绘制X坐标点的文本标签
g.selectAll('text')
.data(xpoints()) // 绑定数据
.enter().append('text') // 创建文本元素
.attr('x', d => x(d.coords[0]) - 8) // 设置文本X位置
.attr('y', d => y(d.coords[1]) + 8) // 设置文本Y位置
.text(d => d.name) // 设置文本内容
.attr('font-family', 'sans-serif') // 设置字体
.attr('font-size', '5px') // 设置字体大小
.attr('fill', 'black'); // 设置文本颜色
// 绘制连接线
g.selectAll('line.connection')
.data(connections()) // 绑定数据
.enter().append('line') // 创建线元素
.attr('class', 'connection') // 设置类名
.attr('x1', d => x(d.X_point[0])) // 连接线起点X
.attr('y1', d => y(d.X_point[1])) // 连接线起点Y
.attr('x2', d => x(d.Y_transformed_point[0])) // 连接线终点X
.attr('y2', d => y(d.Y_transformed_point[1])) // 连接线终点Y
.attr('stroke-width', 1) // 线宽
.attr('stroke', d => {
// 根据条件设置连接线的颜色
if (d.matched === 1) {
return 'green'; // 匹配的连接线为绿色
} else {
return 'orange'; // 其他情况使用橙色
}
});
// 绘制X轴和Y轴
g.append('g')
.attr('transform', `translate(0,${height})`) // 设置Y轴位置
.call(d3.axisBottom(x)); // 绘制X轴
g.append('g')
.call(d3.axisLeft(y)); // 绘制Y轴
// 添加缩放行为
const zoomBehavior = zoom()
.scaleExtent([0.5, 5]) // 设置缩放范围
.on('zoom', (event) => {
g.attr('transform', event.transform); // 应用缩放变换到g元素
updateLenSVG(event.transform); // 更新Len SVG的视图
});
svgMain.call(zoomBehavior); // 应用缩放行为到主SVG上
}
- 设置Len视图
// 绘制辅助SVG的函数
const drawLenSVG = () => {
const svgLen = d3.select(svgLenRef)
.attr("width", 500) // 设置宽度
.attr("height", 400) // 设置高度
.style('margin-left', '10px') // 设置左边距
.style('border', '1px solid black'); // 设置边框样式
// 创建一个g元素用于放置内容
const g = svgLen.append('g');
// 复制主SVG中的内容到辅助SVG
g.html(d3.select(svgMainRef).select('g').html());
// 初始化辅助SVG的视图
updateLenSVG(d3.zoomIdentity);
};
2.1 对Len视图添加g元素,并复制主SVG中的内容到Len SVG
// 创建一个g元素用于放置内容
const g = svgLen.append('g');
// 复制主SVG中的内容到辅助SVG
g.html(d3.select(svgMainRef).select('g').html());
2.2 初始化LenSVG的视图(函数下一步实现)
updateLenSVG(d3.zoomIdentity);
3. 实现updateLenSVG函数
// 更新辅助SVG的缩放函数
function updateLenSVG(transform) {
const svgLen = d3.select(svgLenRef); // 选择辅助SVG
const g = svgLen.select('g'); // 选择g元素
// 获取主SVG的尺寸
const width = +d3.select(svgMainRef).attr('width');
const height = +d3.select(svgMainRef).attr('height');
// 计算放大区域的边界
const centerX = width / 2; // 计算中心X坐标
const centerY = height / 2; // 计算中心Y坐标
const rectX = (centerX - 250) / transform.k - transform.x / transform.k; // 计算边界X
const rectY = (centerY - 200) / transform.k - transform.y / transform.k; // 计算边界Y
// 设置辅助SVG的视图范围
g.attr('transform', `translate(${-rectX * transform.k}, ${-rectY * transform.k}) scale(${transform.k})`);
// 更新辅助SVG中的内容
g.html(d3.select(svgMainRef).select('g').html());
// 重新计算X和Y的最小最大值
const axisXOffset = 10; // X轴偏移
const axisYOffset = 50; // Y轴偏移
const xMin = Math.min(
(d3.min(xpoints(), d => d.coords[0])) - axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) - axisXOffset
);
const xMax = Math.max(
(d3.max(xpoints(), d => d.coords[0])) + axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) + axisXOffset
);
const yMin = Math.min(
(d3.min(xpoints(), d => d.coords[1])) - axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) - axisYOffset
);
const yMax = Math.max(
(d3.max(xpoints(), d => d.coords[1])) + axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) + axisYOffset
);
// 创建新的比例尺
const x = d3.scaleLinear()
.domain([xMin, xMax]) // 输入范围
.range([0, width - 80]); // 输出范围
const y = d3.scaleLinear()
.domain([yMin, yMax]) // 输入范围
.range([height - 50, 0]); // 输出范围
// 更新Y坐标点的圆圈位置
g.selectAll('circle.drawingData')
.data(ypoints()) // 绑定数据
.attr('cx', d => x(d.coords[0])) // 更新X坐标
.attr('cy', d => y(d.coords[1])); // 更新Y坐标
// 移除旧的文本标签
g.selectAll('text.drawingData').remove();
// 重新绘制文本标签
g.selectAll('circle.drawingData')
.each(function(d) {
const circle = d3.select(this);
g.append('text')
.attr('class','drawingData')
.attr('x', circle.attr('cx'))
.attr('y', parseFloat(circle.attr('cy')) - 10)
.text(d.text)
.attr('font-family', 'sans-serif')
.attr('font-size', '10px')
.attr('fill', 'red')
.attr('text-anchor', 'middle');
});
}
3.1 设置Len视图的视图范围,更新内容
const svgLen = d3.select(svgLenRef); // 选择辅助SVG
const g = svgLen.select('g'); // 选择g元素
// 获取主SVG的尺寸
const width = +d3.select(svgMainRef).attr('width');
const height = +d3.select(svgMainRef).attr('height');
// 计算放大区域的边界
const centerX = width / 2; // 计算中心X坐标
const centerY = height / 2; // 计算中心Y坐标
const rectX = (centerX - 250) / transform.k - transform.x / transform.k; // 计算边界X
const rectY = (centerY - 200) / transform.k - transform.y / transform.k; // 计算边界Y
// 设置辅助SVG的视图范围
g.attr('transform', `translate(${-rectX * transform.k}, ${-rectY * transform.k}) scale(${transform.k})`);
// 更新辅助SVG中的内容
g.html(d3.select(svgMainRef).select('g').html());
3.2 因为想使redCircles附带新信息,所以重新绘制
请注意:重新绘制len视图的数据时,其相对位置与main视图中存在偏差,所以要手动测试偏移量viewOffetWidth
、viewOffetHeight
。
// 重新计算X和Y的最小最大值
const axisXOffset = 10; // X轴偏移
const axisYOffset = 50; // Y轴偏移
const xMin = Math.min(
(d3.min(xpoints(), d => d.coords[0])) - axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) - axisXOffset
);
const xMax = Math.max(
(d3.max(xpoints(), d => d.coords[0])) + axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) + axisXOffset
);
const yMin = Math.min(
(d3.min(xpoints(), d => d.coords[1])) - axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) - axisYOffset
);
const yMax = Math.max(
(d3.max(xpoints(), d => d.coords[1])) + axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) + axisYOffset
);
const viewOffetWidth= -80;
const viewOffetHeight= -50;
// 创建新的比例尺
const x = d3.scaleLinear()
.domain([xMin, xMax]) // 输入范围
.range([0, width + viewOffetWidth]); // 输出范围
const y = d3.scaleLinear()
.domain([yMin, yMax]) // 输入范围
.range([height + viewOffetHeight, 0]); // 输出范围
// 更新Y坐标点的圆圈位置
g.selectAll('circle.drawingData')
.data(ypoints()) // 绑定数据
.attr('cx', d => x(d.coords[0])) // 更新X坐标
.attr('cy', d => y(d.coords[1])); // 更新Y坐标
// 移除旧的文本标签
g.selectAll('text.drawingData').remove();
// 重新绘制文本标签
g.selectAll('circle.drawingData')
.each(function(d) {
const circle = d3.select(this);
g.append('text')
.attr('class','drawingData')
.attr('x', circle.attr('cx'))
.attr('y', parseFloat(circle.attr('cy')) - 10)
.text(d.text)
.attr('font-family', 'sans-serif')
.attr('font-size', '10px')
.attr('fill', 'red')
.attr('text-anchor', 'middle');
});
完整代码
- 使用两个SVG进行主视图和Len视图的区分,len视图中附带额外信息
import { createSignal, onMount } from 'solid-js'; // 导入Solid.js库中的createSignal和onMount
import * as d3 from 'd3'; // 导入D3.js库
import { zoom } from 'd3-zoom'; // 从D3.js导入缩放功能
import { Coordinates, XPoint, YTransformedPoint, Connection, MatchData } from "../utils/type"; // 导入类型定义
const Temp = () => {
// 创建状态信号用于存储不同类型的数据
const [data, setData] = createSignal<MatchData | null>(null); // 存储匹配数据
const [xpoints, setXpoints] = createSignal<XPoint[]>([]); // 存储X坐标点
const [ypoints, setYpoints] = createSignal<YTransformedPoint[]>([]); // 存储变换后的Y坐标点
const [connections, setConnections] = createSignal<Connection[]>([]); // 存储连接信息
let svgMainRef: SVGSVGElement | undefined; // 主SVG图形的引用
let svgLenRef: SVGSVGElement | undefined; // 辅助SVG图形的引用
// 组件挂载时执行的数据加载
onMount(async () => {
try {
// 从指定路径获取数据
const response = await fetch('./src/assets/dataset/matched_points_info.json');
if (!response.ok) {
throw new Error(`HTTP 错误!状态码:${response.status}`); // 若响应不OK则抛出错误
}
const jsonData: MatchData = await response.json(); // 解析JSON数据
setData(jsonData); // 设置状态信号中存储的数据
setXpoints(jsonData.X_points); // 设置X坐标点
setYpoints(jsonData.Y_transformed_points); // 设置Y坐标点
setConnections(jsonData.connections); // 设置连接信息
drawMainSVG(); // 调用绘制主SVG的函数
drawLenSVG(); // 调用绘制辅助SVG的函数
} catch (error) {
console.error("加载数据时出错:", error); // 捕获并输出错误信息
}
});
// 绘制主SVG的函数
const drawMainSVG = () => {
// 选择主SVG并设置其属性
const svgMain = d3.select(svgMainRef)
.attr("width", 1000) // 设置宽度
.attr("height", 700) // 设置高度
.style('border', '1px solid black'); // 设置边框样式
// 定义边距
const margin = { top: 20, right: 20, bottom: 30, left: 60 };
// 计算绘图区域的宽度和高度
const width = +svgMain.attr('width') - margin.left - margin.right;
const height = +svgMain.attr('height') - margin.top - margin.bottom;
// 设置X和Y轴的最小最大值
const axisXOffset = 10; // X轴偏移
const axisYOffset = 50; // Y轴偏移
const xMin = Math.min(
(d3.min(xpoints(), d => d.coords[0])) - axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) - axisXOffset
);
const xMax = Math.max(
(d3.max(xpoints(), d => d.coords[0])) + axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) + axisXOffset
);
const yMin = Math.min(
(d3.min(xpoints(), d => d.coords[1])) - axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) - axisYOffset
);
const yMax = Math.max(
(d3.max(xpoints(), d => d.coords[1])) + axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) + axisYOffset
);
// 创建X轴和Y轴的比例尺
const x = d3.scaleLinear()
.domain([xMin, xMax]) // 输入数据范围
.range([0, width]); // 输出像素范围
const y = d3.scaleLinear()
.domain([yMin, yMax]) // 输入数据范围
.range([height, 0]); // 输出像素范围
// 创建一个g元素用于绘制内容
const g = svgMain.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`); // 设置内容位置
// 绘制X坐标点的圆圈
g.selectAll('circle.jsonData')
.data(xpoints()) // 绑定数据
.enter().append('circle') // 创建圆元素
.attr('class', 'jsonData') // 设置类名
.attr('cx', d => x(d.coords[0])) // 设置圆心X坐标
.attr('cy', d => y(d.coords[1])) // 设置圆心Y坐标
.attr('r', 2) // 设置圆的半径
.attr('fill', 'steelblue'); // 设置圆的填充颜色
// 绘制Y坐标点的圆圈
g.selectAll('circle.drawingData')
.data(ypoints()) // 绑定数据
.enter().append('circle') // 创建圆元素
.attr('class', 'drawingData') // 设置类名
.attr('cx', d => x(d.coords[0])) // 设置圆心X坐标
.attr('cy', d => y(d.coords[1])) // 设置圆心Y坐标
.attr('r', 2) // 设置圆的半径
.attr('fill', 'red') // 设置圆的填充颜色
.on('mouseover', (event, d) => { // 鼠标悬停事件
const tooltip = d3.select('body') // 选择body元素创建tooltip
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute') // 绝对定位
.style('background-color', 'white') // 背景色
.style('border', '1px solid black') // 边框样式
.style('padding', '5px') // 内边距
.style('pointer-events', 'none') // 不响应鼠标事件
.style('left', `${event.pageX + 10}px`) // 设置tooltip左位置
.style('top', `${event.pageY + 10}px`) // 设置tooltip顶部位置
.text(d.text); // 显示文本内容
})
.on('mouseout', () => {
d3.select('.tooltip').remove(); // 鼠标移出时移除tooltip
});
// 绘制X坐标点的文本标签
g.selectAll('text')
.data(xpoints()) // 绑定数据
.enter().append('text') // 创建文本元素
.attr('x', d => x(d.coords[0]) - 8) // 设置文本X位置
.attr('y', d => y(d.coords[1]) + 8) // 设置文本Y位置
.text(d => d.name) // 设置文本内容
.attr('font-family', 'sans-serif') // 设置字体
.attr('font-size', '5px') // 设置字体大小
.attr('fill', 'black'); // 设置文本颜色
// 绘制连接线
g.selectAll('line.connection')
.data(connections()) // 绑定数据
.enter().append('line') // 创建线元素
.attr('class', 'connection') // 设置类名
.attr('x1', d => x(d.X_point[0])) // 连接线起点X
.attr('y1', d => y(d.X_point[1])) // 连接线起点Y
.attr('x2', d => x(d.Y_transformed_point[0])) // 连接线终点X
.attr('y2', d => y(d.Y_transformed_point[1])) // 连接线终点Y
.attr('stroke-width', 1) // 线宽
.attr('stroke', d => {
// 根据条件设置连接线的颜色
if (d.matched === 1) {
return 'green'; // 匹配的连接线为绿色
} else {
return 'orange'; // 其他情况使用橙色
}
});
// 绘制X轴和Y轴
g.append('g')
.attr('transform', `translate(0,${height})`) // 设置Y轴位置
.call(d3.axisBottom(x)); // 绘制X轴
g.append('g')
.call(d3.axisLeft(y)); // 绘制Y轴
// 添加缩放行为
const zoomBehavior = zoom()
.scaleExtent([0.5, 5]) // 设置缩放范围
.on('zoom', (event) => {
g.attr('transform', event.transform); // 应用缩放变换到g元素
updateLenSVG(event.transform); // 更新Len SVG的视图
});
svgMain.call(zoomBehavior); // 应用缩放行为到主SVG上
}
// 绘制辅助SVG的函数
const drawLenSVG = () => {
const svgLen = d3.select(svgLenRef)
.attr("width", 500) // 设置宽度
.attr("height", 400) // 设置高度
.style('margin-left', '10px') // 设置左边距
.style('border', '1px solid black'); // 设置边框样式
// 创建一个g元素用于放置内容
const g = svgLen.append('g');
// 复制主SVG中的内容到辅助SVG
g.html(d3.select(svgMainRef).select('g').html());
// 初始化辅助SVG的视图
updateLenSVG(d3.zoomIdentity);
};
// 更新辅助SVG的缩放函数
function updateLenSVG(transform) {
const svgLen = d3.select(svgLenRef); // 选择辅助SVG
const g = svgLen.select('g'); // 选择g元素
// 获取主SVG的尺寸
const width = +d3.select(svgMainRef).attr('width');
const height = +d3.select(svgMainRef).attr('height');
// 计算放大区域的边界
const centerX = width / 2; // 计算中心X坐标
const centerY = height / 2; // 计算中心Y坐标
const rectX = (centerX - 250) / transform.k - transform.x / transform.k; // 计算边界X
const rectY = (centerY - 200) / transform.k - transform.y / transform.k; // 计算边界Y
// 设置辅助SVG的视图范围
g.attr('transform', `translate(${-rectX * transform.k}, ${-rectY * transform.k}) scale(${transform.k})`);
// 更新辅助SVG中的内容
g.html(d3.select(svgMainRef).select('g').html());
// 重新计算X和Y的最小最大值
const axisXOffset = 10; // X轴偏移
const axisYOffset = 50; // Y轴偏移
const xMin = Math.min(
(d3.min(xpoints(), d => d.coords[0])) - axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) - axisXOffset
);
const xMax = Math.max(
(d3.max(xpoints(), d => d.coords[0])) + axisXOffset,
(d3.min(ypoints(), d => d.coords[0])) + axisXOffset
);
const yMin = Math.min(
(d3.min(xpoints(), d => d.coords[1])) - axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) - axisYOffset
);
const yMax = Math.max(
(d3.max(xpoints(), d => d.coords[1])) + axisYOffset,
(d3.min(ypoints(), d => d.coords[1])) + axisYOffset
);
// 创建新的比例尺
const x = d3.scaleLinear()
.domain([xMin, xMax]) // 输入范围
.range([0, width - 80]); // 输出范围
const y = d3.scaleLinear()
.domain([yMin, yMax]) // 输入范围
.range([height - 50, 0]); // 输出范围
// 更新Y坐标点的圆圈位置
g.selectAll('circle.drawingData')
.data(ypoints()) // 绑定数据
.attr('cx', d => x(d.coords[0])) // 更新X坐标
.attr('cy', d => y(d.coords[1])); // 更新Y坐标
// 移除旧的文本标签
g.selectAll('text.drawingData').remove();
// 重新绘制文本标签
g.selectAll('circle.drawingData')
.each(function(d) {
const circle = d3.select(this);
g.append('text')
.attr('class','drawingData')
.attr('x', circle.attr('cx'))
.attr('y', parseFloat(circle.attr('cy')) - 10)
.text(d.text)
.attr('font-family', 'sans-serif')
.attr('font-size', '10px')
.attr('fill', 'red')
.attr('text-anchor', 'middle');
});
}
// 返回包含两个SVG图形的JSX元素
return (
<div style={{ display: 'flex' }}>
<svg ref={svgMainRef}></svg> {/* 主SVG */}
<svg ref={svgLenRef}></svg> {/* 辅助SVG */}
</div>
);
}
export default Temp; // 导出Temp组件
标签:const,14,SVG,知识,transform,SolidJS,coords,d3,attr
From: https://www.cnblogs.com/Frey-Li/p/18414990