提交 c2b586b3 authored 作者: 祖铭松's avatar 祖铭松

feat: update

上级 bcb22ad3
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"d3": "^4.5.0", "d3": "^4.5.0",
"element-plus": "^2.3.7", "element-plus": "^2.3.7",
"hclusterjs": "^1.3.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"nanoid": "^5.0.1", "nanoid": "^5.0.1",
"plotly.js-dist": "^2.23.2", "plotly.js-dist": "^2.23.2",
......
...@@ -4,306 +4,124 @@ ...@@ -4,306 +4,124 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
// import * as d3 from 'd3'; import { data, verticalData, normalizedData, rootData, vertRootData } from './mock.js';
import { chartData, treeData } from './mock.js';
import * as d3 from 'https://cdn.jsdelivr.net/npm/d3@7/+esm'; import * as d3 from 'https://cdn.jsdelivr.net/npm/d3@7/+esm';
import hclust from 'hclusterjs';
const chartRef = ref(null); const chartRef = ref(null);
// console.log(data)
const data = chartData.map((data, index) => ({ index: index + 1, ...data }));
// console.log(data);
// 生成图表 // 生成图表
const generateChart = () => { const generateChart = () => {
let width = 500; // console.log(hclust);
let height = Math.min(width, 500); // return;
let margin = 10;
const color = (max, range_count, d) => { let radius = 3;
return d3 let dendWidth = 150;
.scaleQuantize() let heatWidth = 462;
.domain([0, max]) let heatHeight = 600;
.range(['#397899', '#60B8E6', '#5C8EE6', '#476EB3', '#E84A43', '#EF8681', '#F7BEBB', '#579967', '#77E691', '#F08230']); let n = 100;
// .range(d3.merge([[d3.color('steelblue')], d3.quantize((t) => d3.interpolateRainbow(t), range_count - 1).reverse()])) let fullWidth = 632;
}; let fullHeight = 770;
let nonMeasurements = ['MouseID', 'Genotype', 'Treatment', 'Behavior', 'class'];
const arc = (index, total) => {
const off = 1; const tree = (data) => {
const radius = (i) => ((Math.min(width, height) / 2) * (i + off + 1)) / (total + off + 3); const root = d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height));
root.dx = radius;
root.dy = dendWidth / (root.height + 1);
return d3 return d3
.arc() .cluster()
.innerRadius(radius(index - 1) + 5) .size([heatHeight, dendWidth])
.outerRadius(radius(index)); .separation(() => 2)(root);
}; };
// const clustering = hclust()
const arcTotal = (index, total) => { // .distance('angular') // support for 'euclidean' and 'angular'
const off = 1; // .linkage('avg') // support for 'avg', 'max' and 'min'
const radius = (i) => ((Math.min(width, height) / 2) * (i + off + 1)) / (total + off + 3); // .posKey('normalized') // 'position' by default
// .data(normalizedData);
return d3
.arc() // const verticalClustering = hclust()
.innerRadius(radius(index - 1) + 20) // .distance('angular') // support for 'euclidean' and 'angular'
.outerRadius(radius(index + 2)); // .linkage('avg') // support for 'avg', 'max' and 'min'
}; // .posKey('normalized') // 'position' by default
// .data(verticalData);
const arcCustom = (index, total) => {
const off = 8; const vertTree = (data) => {
const radius = (i) => ((Math.min(width, height) / 2) * (i + off + 1)) / (total + off + 3); const root = d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height));
root.dy = radius;
root.dx = dendWidth / (root.height + 1);
return d3 return d3
.arc() .cluster()
.innerRadius(radius(index - 1)) .size([heatWidth, dendWidth])
.outerRadius(radius(index)); .separation(() => 2)(root);
}; };
const pie = d3.pie().sort(null).value(1); // const root = tree(clustering.tree());
// const vertRoot = vertTree(verticalClustering.tree());
let categories = Object.keys(data[0]).splice(1); const root = tree(rootData);
const vertRoot = tree(vertRootData);
let cleaned = categories.map((c) => {
return {
name: c,
data: pie(
data
.filter((d) => !!d.index)
.map((d) => {
return {
index: d.index,
value: d[c]
};
})
)
};
});
const svg = d3 const svg = d3
.create('svg') .create('svg')
.attr('width', width) .attr('width', 800)
.attr('height', height) .attr('height', 800)
.attr('viewBox', [-width / 2, -height / 2, width, height]); .attr('viewBox', [0, 0, fullWidth, fullHeight]);
let dataMaxNumber = null;
const dataMaxArr = [];
for (var i = 0; i < cleaned.length; i++) {
let set = new Set(cleaned[i].data.map((f) => f.data.value));
let distinct_values = set.size;
let maxNumber = d3.max(cleaned[i].data.map((f) => f.data.value));
let fcolors = color(maxNumber, distinct_values);
dataMaxArr.push(maxNumber);
svg.append('g')
.attr('stroke', 'none')
.attr('stroke-width', 1)
.attr('stroke-linejoin', 'round')
.selectAll('path' + i)
.data(cleaned[i].data.slice(0, 6))
.attr('id', function (d, i) {
return 'catArc_' + i;
})
.join('path')
.attr('fill', (d) => fcolors(d.data.value))
// .attr('d', i == cleaned.length - 1 ? arcTotal(i, cleaned.length) : arc(i, cleaned.length));
.attr('d', arcCustom(i, cleaned.length))
.attr('transform', 'rotate(90)');
svg.append('g')
.attr('stroke', 'none')
.attr('stroke-width', 1)
.attr('stroke-linejoin', 'round')
.selectAll('path' + i)
.data(cleaned[i].data.slice(6, 12))
.attr('id', function (d, i) {
return 'catArc_' + i;
})
.join('path')
.attr('fill', (d) => fcolors(d.data.value))
.attr('d', arcCustom(i, cleaned.length))
.attr('transform', 'rotate(130)');
svg.append('g')
.attr('stroke', 'none')
.attr('stroke-width', 1)
.attr('stroke-linejoin', 'round')
.selectAll('path' + i)
.data(cleaned[i].data.slice(12, 24))
.attr('id', function (d, i) {
return 'catArc_' + i;
})
.join('path')
.attr('fill', (d) => fcolors(d.data.value))
.attr('d', arcCustom(i, cleaned.length))
.attr('transform', 'rotate(155)');
}
dataMaxNumber = Math.round(Math.max.apply(null, dataMaxArr));
svg.append('text').attr('x', 200).attr('y', 120).style('text-anchor', 'middle').style('font', '14px sans-serif').text('A');
svg.append('text').attr('x', -200).attr('y', 120).style('text-anchor', 'middle').style('font', '14px sans-serif').text('B');
svg.append('text').attr('x', 0).attr('y', -230).style('text-anchor', 'middle').style('font', '14px sans-serif').text('C');
svg.append('g')
.selectAll()
.data(categories)
.join('g')
.attr('transform', (d, i, nodes) => `rotate(65) translate(-10,${(nodes.length / 2 - i - 1) * 15})`)
.call((g) =>
g
.append('text')
.attr('x', 15)
.attr('y', -155)
.attr('dy', '0.35em')
.style('font', '10px sans-serif')
.text((d) => d)
);
// legend
svg.append('g') svg.append('g')
.selectAll()
.data(['0', '1', '2', '3', '4', '5'])
.join('g')
.attr('transform', (d, i, nodes) => `translate(-10,${(nodes.length / 2 - i - 1) * 20})`)
.call((g) => g.append('rect').attr('width', 18).attr('height', 18).attr('fill', color(dataMaxNumber)))
.call((g) =>
g
.append('text')
.attr('x', 24)
.attr('y', 9)
.attr('dy', '0.35em')
.text((d) => d)
);
// tree
generateTree(svg);
// Append the SVG element.
chartRef.value.append(svg.node());
};
const generateTree = (svg) => {
const outerRadius = 277;
const innerRadius = outerRadius - 162;
const linkStep = (startAngle, startRadius, endAngle, endRadius) => {
const c0 = Math.cos((startAngle = ((startAngle - 90) / 180) * Math.PI));
const s0 = Math.sin(startAngle);
const c1 = Math.cos((endAngle = ((endAngle - 90) / 180) * Math.PI));
const s1 = Math.sin(endAngle);
return (
'M' +
startRadius * c0 +
',' +
startRadius * s0 +
(endAngle === startAngle
? ''
: 'A' +
startRadius +
',' +
startRadius +
' 0 0 ' +
(endAngle > startAngle ? 1 : 0) +
' ' +
startRadius * c1 +
',' +
startRadius * s1) +
'L' +
endRadius * c1 +
',' +
endRadius * s1
);
};
// const linkConstant = (d) => {
// return linkStep(d.source.x, d.source.y, d.target.x, d.target.y);
// };
const linkVariable = (d) => {
return linkStep(d.source.x, d.source.radius, d.target.x, d.target.radius);
};
const maxLength = (d) => d.data.length + (d.children ? d3.max(d.children, maxLength) : 0);
const setRadius = (d, y0, k) => {
d.radius = (y0 += d.data.length) * k;
if (d.children) {
d.children.forEach((d) => setRadius(d, y0, k));
}
};
// tree-A
const clusterA = d3
.cluster()
.size([67, innerRadius])
.separation((a, b) => 1);
const rootA = d3
.hierarchy(treeData.treeA, (d) => d.branchset)
.sum((d) => (d.branchset ? 0 : 1))
.sort((a, b) => a.value - b.value || d3.ascending(a.data.length, b.data.length));
clusterA(rootA);
setRadius(rootA, (rootA.data.length = 0), innerRadius / maxLength(rootA));
const linkA = svg
.append('g')
.attr('class', 'tree')
.attr('fill', 'none')
.attr('stroke', '#FF5252')
.selectAll('path')
.data(rootA.links().slice(1))
.join('path')
.each(function (d) {
d.target.linkNode = this;
})
.attr('d', linkVariable)
.attr('stroke', (d) => d.target.color)
.attr('transform', 'rotate(90)');
// tree-B
const clusterB = d3
.cluster()
.size([67, innerRadius])
.separation((a, b) => 1);
const rootB = d3
.hierarchy(treeData.treeB, (d) => d.branchset)
.sum((d) => (d.branchset ? 0 : 1))
.sort((a, b) => a.value - b.value || d3.ascending(a.data.length, b.data.length));
clusterB(rootB);
setRadius(rootB, (rootB.data.length = 0), innerRadius / maxLength(rootB));
const linkB = svg
.append('g')
.attr('class', 'tree')
.attr('fill', 'none') .attr('fill', 'none')
.attr('stroke', '#FF5252') .attr('stroke', '#555')
.attr('stroke-width', 1)
.attr('transform', `translate(${margin}, ${margin + dendWidth})`)
.selectAll('path') .selectAll('path')
.data(rootB.links().slice(1)) .data(root.links())
.join('path') .join('path')
.each(function (d) { .attr(
d.target.linkNode = this; 'd',
}) (d) => `
.attr('d', linkVariable) M${d.target.y},${d.target.x}
.attr('stroke', (d) => d.target.color) L${d.source.y},${d.target.x}
.attr('transform', 'rotate(197)'); ${d.source.y},${d.source.x}
${d.source.y},${d.source.x}
`
);
// tree-C svg.append('g')
const clusterC = d3
.cluster()
.size([140, innerRadius])
.separation((a, b) => 1);
const rootC = d3
.hierarchy(treeData.treeC, (d) => d.branchset)
.sum((d) => (d.branchset ? 0 : 1))
.sort((a, b) => a.value - b.value || d3.ascending(a.data.length, b.data.length));
clusterC(rootC);
setRadius(rootC, (rootC.data.length = 0), innerRadius / maxLength(rootC));
const linkC = svg
.append('g')
.attr('class', 'tree')
.attr('fill', 'none') .attr('fill', 'none')
.attr('stroke', '#FF5252') .attr('stroke', '#555')
.attr('stroke-width', 1)
.attr('transform', `translate(${margin + dendWidth}, ${margin})`)
.selectAll('path') .selectAll('path')
.data(rootC.links().slice(1)) .data(vertRoot.links())
.join('path') .join('path')
.each(function (d) { .attr(
d.target.linkNode = this; 'd',
}) (d) => `
.attr('d', linkVariable) M${d.target.x},${d.target.y}
.attr('stroke', (d) => d.target.color) L${d.target.x},${d.source.y}
.attr('transform', 'rotate(-72)'); ${d.source.x},${d.source.y}
${d.source.x},${d.source.y}
`
);
return svg.node(); let orderingx = vertRoot.leaves().map((el) => el.data.indexes[0]);
svg.append('g')
.attr('transform', `translate(${margin + dendWidth}, ${margin + dendWidth})`)
.selectAll('g')
.data(root.leaves())
.join('g')
.attr('transform', (d) => `translate(0, ${d.x - radius})`)
.selectAll('rect')
.data((d) => d.data.normalized.map((e, i) => d.data.normalized[orderingx[i]]))
.join('rect')
.attr('x', (d, i) => i * 2 * radius)
.attr('y', 0)
.attr('width', 2 * radius)
.attr('height', 2 * radius)
.attr('fill', (d) => d3.interpolateViridis(d));
chartRef.value.append(svg.node());
}; };
onMounted(() => { onMounted(() => {
// generateChart(); generateChart();
}); });
</script> </script>
......
No preview for this file type
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论