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

feat: update

上级 e017baf8
......@@ -27,6 +27,7 @@ import {
setSilhouetteHeatmap,
setSilhouetteLegend
} from '../utils/index';
import { config } from 'plotly.js-dist';
const chartRef = ref(null);
......@@ -68,47 +69,22 @@ const generateChart = (chartData) => {
.attr('viewBox', [0, 0, width, height])
.attr('style', 'max-width: 100%; height: auto; font: 10px sans-serif;');
// 最大值 最小值
const { maxNumber, minNumber } = setMaxMinNumber(chartData);
// color config
const colorConfig = setColor(minNumber, maxNumber);
let configObj = {
...ret,
svg,
width,
height,
radius,
margin,
maxNumber,
minNumber,
dendWidth,
titleConfig,
xAxisLength,
heatWidth,
heatHeight,
color: colorConfig,
...ret
heatHeight
};
const rootConfig = getRootConfig(configObj, chartData);
configObj = { ...configObj, ...rootConfig };
// heatmap
const heatmapRect = setHeatmap(configObj);
setTips(heatmapRect);
// Tree
setTree(configObj);
// axisLabel
setAxisTitle(configObj);
// legend
ret.show_legend && setLegend(configObj);
// label
setLabel(configObj);
// Consensus matrix
generateHeatmap(configObj, chartData);
// Basis
generageBasis(configObj);
// Consensus
......@@ -122,6 +98,42 @@ const generateChart = (chartData) => {
chartRef.value.appendChild(svgInstance.value);
};
// Consensus matrix
const generateHeatmap = (config, chartData) => {
// max/min number
const { maxNumber, minNumber } = setMaxMinNumber(chartData);
// root config
const { root, vertRoot, orderingx } = getRootConfig(config, chartData);
// color config
let colors = [
'#313695',
'#436FB1',
'#6BA2CB',
'#9BCCE2',
'#F0F9D8',
'#F0F9D8',
'#FEF0A9',
'#FDCD7E',
'#FA9C58',
'#EE613D',
'#D22B26',
'#A50026'
];
const color = setColor(minNumber, maxNumber, colors);
const configObj = { ...config, root, vertRoot, orderingx, maxNumber, minNumber, color };
// heatmap
const heatmapRect = setHeatmap(configObj);
setTips(heatmapRect);
// Tree
setTree(configObj);
// axisLabel
setAxisTitle(configObj);
// legend
configObj.show_legend && setLegend(configObj);
// label
setLabel(configObj);
};
// Basis
const generageBasis = (config) => {
const chartData = chartMockData.slice(0, 2);
......
......@@ -131,7 +131,7 @@ export const setHeatmap = (config) => {
.selectAll('g')
.data(config.root.leaves())
.join('g')
.attr('transform', (d, i) => `translate(0, ${d.x - config.radius})`)
.attr('transform', (d, i) => `translate(0, ${config.radius * 2 * i})`)
.selectAll('rect')
.data((d) => d.data.values.map((e, i) => d.data.values[config.orderingx[i]]))
.join('rect')
......@@ -245,10 +245,11 @@ export const setLegend = (config) => {
const legendData = [...Array.from({ length: config.maxNumber }, (item, i) => i)];
config.svg.append('g')
.attr('transform', `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 80}, ${config.margin + config.dendWidth * 2})`)
.selectAll()
.data(legendData)
.join('g')
.attr('transform', (d, i, nodes) => `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 80}, ${config.height / 3 + (nodes.length / 2 - i - 1) * 18 - 60})`)
.attr('transform', (d, i, nodes) => `translate(0, ${(nodes.length - i) * 18 - 18})`)
.call((g) => g.append('rect').attr('width', 18).attr('height', 18).attr('fill', config.color()))
.call((g) =>
g
......@@ -264,12 +265,10 @@ export const setLegend = (config) => {
// Basis legend
export const setBasisLegend = (config) => {
const legendData = [...Array.from({ length: config.maxNumber }, (item, i) => i)];
const textX = config.dendWidth + config.xAxisLength * (config.radius * 2) + 180
config.svg.append('text')
.attr('x', textX)
.attr('y', config.height / 3 - 110)
.attr('y', config.margin + config.dendWidth * 2)
.attr('fill', '#333333')
.attr('font-size', '20px')
.attr('font-family', `${config.titleConfig.fontFamily}`)
......@@ -279,10 +278,11 @@ export const setBasisLegend = (config) => {
.text('basis');
config.svg.append('g')
.attr('transform', `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 160}, ${config.margin + config.dendWidth * 2})`)
.selectAll()
.data(legendData)
.join('g')
.attr('transform', (d, i, nodes) => `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 160}, ${config.height / 3 + (nodes.length / 2 - i - 1) * 21 - 50})`)
.attr('transform', (d, i, nodes) => `translate(0, ${i * 21 + 10})`)
.call((g) => g.append('rect').attr('width', 15).attr('height', 15).attr('fill', config.color()))
.call((g) =>
g
......@@ -298,12 +298,11 @@ export const setBasisLegend = (config) => {
// Consensus legend
export const setConsensusLegend = (config) => {
const legendData = [...Array.from({ length: config.maxNumber }, (item, i) => i)];
const textX = config.dendWidth + config.xAxisLength * (config.radius * 2) + 205
const textX = config.dendWidth + config.xAxisLength * (config.radius * 2) + 200
let yRatio = 175;
config.svg.append('text')
.attr('x', textX)
.attr('y', config.height / 3 + 50)
.attr('y', config.margin + config.dendWidth * 2 + yRatio)
.attr('fill', '#333333')
.attr('font-size', '20px')
.attr('font-family', `${config.titleConfig.fontFamily}`)
......@@ -313,10 +312,11 @@ export const setConsensusLegend = (config) => {
.text('consensus');
config.svg.append('g')
.attr('transform', `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 160}, ${config.margin + config.dendWidth * 2 + yRatio})`)
.selectAll()
.data(legendData)
.join('g')
.attr('transform', (d, i, nodes) => `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 160}, ${config.height / 3 + (nodes.length / 2 - i - 1) * 21 + 110})`)
.attr('transform', (d, i, nodes) => `translate(0, ${i * 21 + 10})`)
.call((g) => g.append('rect').attr('width', 15).attr('height', 15).attr('fill', config.color()))
.call((g) =>
g
......@@ -331,12 +331,12 @@ export const setConsensusLegend = (config) => {
// Silhouette legend
export const setSilhouetteLegend = (config) => {
const textX = config.dendWidth + config.xAxisLength * (config.radius * 2) + 200
const legendData = [config.maxNumber, config.minNumber]
const textX = config.dendWidth + config.xAxisLength * (config.radius * 2) + 200
let yRatio = 350;
config.svg.append('text')
.attr('x', textX)
.attr('y', config.height / 3 + 210)
.attr('y', config.dendWidth * 2 + yRatio)
.attr('fill', '#333333')
.attr('font-size', '20px')
.attr('font-family', `${config.titleConfig.fontFamily}`)
......@@ -346,11 +346,12 @@ export const setSilhouetteLegend = (config) => {
.text('silhouette');
config.svg.append('g')
.attr('transform', `translate(${config.dendWidth + config.xAxisLength * (config.radius * 2) + 160}, ${config.margin + config.dendWidth * 2 + yRatio})`)
.selectAll()
.data(legendData)
.join('text')
.attr('x', textX + 20)
.attr('y', (d, i) => config.height / 3 + i * 40 + 240)
.attr('x', 0 + 60)
.attr('y', (d, i) => i * 40 + 15 )
.attr('fill', config.titleConfig.color)
.attr('font-style', `${config.titleConfig.fontStyle}`)
.attr('font-weight', `${config.titleConfig.fontWeight}`)
......@@ -383,7 +384,7 @@ export const setSilhouetteLegend = (config) => {
.attr('height', '60')
.attr("fill", "url(#mylLnearGradient)")
.attr("x", `${config.dendWidth + config.xAxisLength * (config.radius * 2) + 160}`)
.attr("y", `${config.height / 3 + 225}`)
.attr("y", `${config.margin + config.dendWidth * 2 + yRatio}`)
}
// 标签
......@@ -391,15 +392,14 @@ export const setAxisTitle = (config) => {
const labelList = config.root.leaves().map((item) => item.data.id);
config.svg.append('g')
.selectAll('g')
.data(labelList)
.join('g')
.attr(
'transform',
(d, i) =>
`translate(${config.margin + config.dendWidth + config.xAxisLength * 2 * config.radius + 4},${config.margin + config.dendWidth * 2 + i * 2 * config.radius + 4
})`
`translate(${config.margin + config.dendWidth + config.xAxisLength * 2 * config.radius + 4},${config.margin + config.dendWidth * 2})`
)
.selectAll('g')
.data(labelList)
.join('g')
.attr('transform', (d, i) => `translate(0,${i * 2 * config.radius + 3})`)
.call((g) =>
g
.append('text')
......
<template>
<div ref="chartRef" class="chart-box">
<div class="tip"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { chartMockData } from '../mock.js';
import * as d3 from 'https://cdn.jsdelivr.net/npm/d3@7/+esm';
import PubSub from 'pubsub-js';
import {
getRootConfig,
setColor,
setTips,
setMaxMinNumber,
setHeatmap,
setLegend,
setBasisLegend,
setConsensusLegend,
setAxisTitle,
setLabel,
setBasisHeatmap,
setConsensusHeatmap,
setSilhouetteHeatmap,
setSilhouetteLegend
} from '../utils/index';
const chartRef = ref(null);
// 配置项数据
const options = ref({});
// 图表实例
const svgInstance = ref(null);
// pubsub实例
const pubSubInstance = ref(null);
// 生成图表
const generateChart = () => {
const chartData = chartMockData.slice(50).map((item) => ({ ...item, values: item.values.slice(0, 10) }));
const ret = options.value;
let width = Number(ret.chart_width) || 1000;
let height = Number(ret.chart_height) || 1000;
let margin = 50;
let radius = 3;
let dendWidth = 0;
let xAxisLength = chartData[0].values.length;
const heatWidth = 2 * radius * xAxisLength;
const heatHeight = 2 * radius * chartData.length;
const titleConfig = {
label: !!ret.title ? ret.title : null,
color: ret.title_font_color || '#333333',
fontSize: ret.title_font_size || 20,
fontStyle: ret.title_font_style ? 'oblique' : 'normal',
fontFamily: ret.title_font_family || 'Arial',
fontWeight: ret.title_font_weight ? 'bold' : 'normal'
};
const svg = d3
.create('svg')
.attr('width', width)
.attr('height', height + 100)
.attr('viewBox', [0, 0, width, height])
.attr('style', 'max-width: 100%; height: auto; font: 10px sans-serif;');
let configObj = {
svg,
width,
height,
radius,
margin,
dendWidth,
titleConfig,
xAxisLength,
heatWidth,
heatHeight,
...ret
};
const chartData1 = chartMockData.slice(0, 50).map((item) => ({ ...item, values: item.values.slice(0, 10) }));
const chartData2 = chartMockData.slice(50).map((item) => ({ ...item, values: item.values.slice(10, 20) }));
const chartData3 = chartMockData.slice(50).map((item) => ({ ...item, values: item.values.slice(20, 30) }));
const chartData4 = chartMockData.slice(50).map((item) => ({ ...item, values: item.values.slice(30, 40) }));
const chartData5 = chartMockData.slice(50).map((item) => ({ ...item, values: item.values.slice(40, 50) }));
const chartDataArr = [chartData1, chartData2, chartData3, chartData4, chartData5];
for (let i = 0; i < chartDataArr.length; i++) {
setRankChart(configObj, chartDataArr[i], i);
}
svgInstance.value = svg.node();
// Append the SVG element.
chartRef.value.appendChild(svgInstance.value);
};
const setRankChart = (config, chartData, index) => {
const title = `rank = ${index + 2}`;
let xRatio = 350;
let yRatio = 500;
if (index <= 2) {
xRatio = index * xRatio;
yRatio = 0;
} else if (index == 3) {
xRatio = 0;
}
let xAxisLength = chartData[0].values.length;
// 位置
const position = {
title: [config.margin + xRatio, 0 + yRatio],
heatmap: [10 + xRatio, config.margin + yRatio],
legend: [xAxisLength * (config.radius * 2) + 70 + xRatio, config.margin + yRatio],
axisLabel: [xAxisLength * (config.radius * 2) + 12 + xRatio, config.margin + yRatio]
};
const rootConfig = getRootConfig(config, chartData);
// 最大值 最小值
const { maxNumber, minNumber } = setMaxMinNumber(chartData);
// color config
let colors = [
'#313695',
'#436FB1',
'#6BA2CB',
'#9BCCE2',
'#F0F9D8',
'#F0F9D8',
'#FEF0A9',
'#FDCD7E',
'#FA9C58',
'#EE613D',
'#D22B26',
'#A50026'
];
const color = setColor(minNumber, maxNumber, colors);
const configObj = { ...config, ...rootConfig, maxNumber, minNumber, color, title, position };
// heatmap
const heatmapRect = setHeatmap(configObj);
// tip
setTips(heatmapRect);
// axisLabel
setAxisTitle(configObj);
// legend
configObj.show_legend && setLegend(configObj);
// label
setLabel(configObj);
};
const setConsensus = (config) => {
const chartData = chartMockData.slice(0, 2);
// max/min number
const { maxNumber, minNumber } = setMaxMinNumber(chartData);
// root config
const { root, orderingx } = getRootConfig(config, chartData);
// color config
const colors = ['#9D9FFF', '#F57BFF', '#3AB1FF', '#D38BFF', '#00C745', '#F4952C'];
const color = setColor(minNumber, maxNumber, colors);
const configObj = { ...config, root, orderingx, maxNumber, minNumber, color };
const heatmapRect = setBasisHeatmap(configObj);
// tips
setTips(heatmapRect);
// legend
config.show_legend && setBasisLegend(configObj);
};
onMounted(() => {
pubSubInstance.value = PubSub.subscribe('options', (m, data) => {
options.value = data;
chartRef.value.removeChild(svgInstance.value);
generateChart();
});
generateChart();
});
onUnmounted(() => {
PubSub.unsubscribe(pubSubInstance.value);
});
</script>
<style scoped>
.chart-box {
position: relative;
}
</style>
<template>
<div class="form-container">
<h3 class="type content">画布</h3>
<div class="form-item">
<span>宽:</span>
<input type="text" v-model="dynamic_parameter.chart_width" />
</div>
<div class="form-item">
<span>高:</span>
<input type="text" v-model="dynamic_parameter.chart_height" />
</div>
<h3 class="type content">文本</h3>
<div class="form-item">
<span>标题:</span>
<input type="text" v-model="dynamic_parameter.title" />
</div>
<div class="form-item">
<span>字体:</span>
<input type="text" v-model="dynamic_parameter.title_font_family" />
</div>
<div class="form-item">
<span>字号:</span>
<input type="text" v-model="dynamic_parameter.title_font_size" />
</div>
<div class="form-item">
<span>字体颜色:</span>
<input type="text" v-model="dynamic_parameter.title_font_color" />
</div>
<div class="form-item">
<span>字体加粗:</span>
<input type="checkbox" v-model="dynamic_parameter.title_font_weight" />
</div>
<div class="form-item">
<span>斜体:</span>
<input type="checkbox" v-model="dynamic_parameter.title_font_style" />
</div>
<h3 class="type content">图例</h3>
<div class="form-item">
<span>显示图例:</span>
<input type="checkbox" v-model="dynamic_parameter.show_legend" />
</div>
<div class="btn">
<button @click="submit">提交</button>
</div>
</div>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
import PubSub from 'pubsub-js';
//动态图参数,固定参数
const dynamic_parameter = reactive({
//长宽间距
chart_width: 1000,
chart_height: 1000,
margin_top: 60,
margin_right: 60,
margin_bottom: 60,
margin_left: 60,
//标题配置
title: 'Consensus matrix',
title_font_family: 'Arial',
title_font_weight: true,
title_font_size: 20,
title_font_color: '#333333',
title_font_style: false,
//横轴标题
xtitle_font_family: 'Arial',
xtitle_font_weight: false,
xtitle_font_style: false,
xtitle_font_size: 16,
xtitle_font_color: '#333333',
//纵轴标题
ytitle_font_family: 'Arial',
ytitle_font_weight: false,
ytitle_font_style: false,
ytitle_font_size: 16,
ytitle_font_color: '#333333',
//颜色设置
data_color_list: [],
//图例
show_legend: true,
legend_position: '0,0',
//坐标轴
show_x_axis: true,
show_x_line: true,
show_x_tickMarks: true,
show_x_gridLine: false,
show_x_title: true,
x_title: '',
x_tickMarks_color: '#333333',
x_tickMarks_length: '5',
show_y_axis: true,
show_y_line: true,
show_y_tickMarks: true,
show_y_gridLine: false,
show_y_title: true,
y_title: '',
y_tickMarks_color: '#333333',
y_tickMarks_length: '5',
//节点相关的参数,可能还会有更多
node_id: null, //节点id
node_color: null, //节点颜色
is_delect_node: false, //是否删除节点
node_search_name: null, //节点名称搜索
node_search_color: '#FF0404', //节点搜索后的颜色
node_edit_name: null, //修改节点名称
node_height: 100, //节点大小
path_id: null
});
const submit = () => {
PubSub.publish('options', dynamic_parameter);
};
onMounted(() => {
submit();
});
</script>
<style scoped>
.form-item span {
display: inline-block;
width: 100px;
text-align: left;
}
.btn {
width: 100%;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<template>
<div class="chart-container">
<Chart />
<Config />
</div>
</template>
<script setup>
import Chart from './components/chart.vue';
import Config from './components/config.vue';
</script>
<style scoped>
.chart-container {
display: flex;
}
</style>
This source diff could not be displayed because it is too large. You can view the blob instead.
import distance from 'distancejs';
import extend from 'extend';
/**
* hcluster
* @returns
*/
export default function () {
var data,
clusters,
clustersGivenK,
treeRoot,
posKey = 'position',
distanceName = 'angular',
distanceFn = distance.angular,
linkage = 'avg',
verbose = false;
//
// simple constructor
function clust() { }
//
// getters, setters a la D3
// return data or set data and build tree
clust.data = function (value) {
if (!arguments.length) return data;
// dataset will be mutated
data = value;
clust._buildTree();
return clust;
};
clust.posKey = function (value) {
if (!arguments.length) return posKey;
posKey = value;
return clust;
};
clust.linkage = function (value) {
if (!arguments.length) return linkage;
linkage = value;
return clust;
};
clust.verbose = function (value) {
if (!arguments.length) return verbose;
verbose = value;
return clust;
};
clust.distance = function (value) {
if (!arguments.length) return distanceName;
distanceName = value;
distanceFn = {
angular: distance.angular,
euclidean: distance.euclidean
}[value] || distance.angular;
return clust;
}
//
// get tree properties
clust.orderedNodes = function () {
if (!treeRoot) throw new Error('Need to passin data and build tree first.');
return treeRoot.indexes.map(function (ndx) {
return data[ndx];
});
};
clust.tree = function () {
if (!treeRoot) throw new Error('Need to passin data and build tree first.');
return treeRoot;
};
clust.getClusters = function (n) {
if (!treeRoot) throw new Error('Need to passin data and build tree first.');
if (n > data.length) throw new Error('n must be less than the size of the dataset');
return clustersGivenK[data.length - n]
.map(function (indexes) {
return indexes.map(function (ndx) { return data[ndx]; });
});
};
//
// math, matrix utility fn's
// return unique pairs of indexes on n x n matrix above the diagonal
clust._squareMatrixPairs = function (n) {
var pairs = [];
for (var row = 0; row < n; row++) {
for (var col = row + 1; col < n; col++) {
pairs.push([row, col]);
}
}
return pairs;
};
// average distance between set of cluster indexes
clust._avgDistance = function (setA, setB) {
var distance = 0;
for (var ndxA = 0; ndxA < setA.length; ndxA++) {
for (var ndxB = 0; ndxB < setB.length; ndxB++) {
distance += data[setA[ndxA]]._distances[setB[ndxB]];
}
}
return distance / setA.length / setB.length;
};
// min distance between set of cluster indexes
clust._minDistance = function (setA, setB) {
var distances = [];
for (var ndxA = 0; ndxA < setA.length; ndxA++) {
for (var ndxB = 0; ndxB < setB.length; ndxB++) {
distances.push(data[setA[ndxA]]._distances[setB[ndxB]]);
}
}
return distances.sort()[0];
};
// max distance between set of cluster indexes
clust._maxDistance = function (setA, setB) {
var distances = [];
for (var ndxA = 0; ndxA < setA.length; ndxA++) {
for (var ndxB = 0; ndxB < setB.length; ndxB++) {
distances.push(data[setA[ndxA]]._distances[setB[ndxB]]);
}
}
return distances.sort()[distances.length - 1];
};
//
// tree construction
//
clust._buildTree = function () {
if (!data || !data.length) throw new Error('Need `data` to build tree');
//
var node, clusterPairs, nearestPair, newCluster;
clusters = [];
clustersGivenK = [];
treeRoot = {};
// calculate distances and build single datum clusters
data.forEach(function (d, ndx) {
d._distances = data.map(function (compareTo) {
return distanceFn(d[posKey], compareTo[posKey]);
});
clusters.push(extend(d, {
height: 0,
indexes: [ndx]
}));
});
// for tree of n leafs, n-1 linkages
for (var iter = 0; iter < data.length - 1; iter++) {
verbose && console.log(iter + ': ' +
clusters.map(function (c) { return c.indexes; }).join('|'));
// find closest pair of clusters, pair[2] is distance
clusterPairs = clust._squareMatrixPairs(clusters.length);
clusterPairs.forEach(function (pair) {
pair[2] = clust['_' + linkage + 'Distance'](
clusters[pair[0]].indexes,
clusters[pair[1]].indexes);
});
nearestPair = clusterPairs
.reduce(function (pairA, pairB) { return pairA[2] <= pairB[2] ? pairA : pairB; },
[0, 0, Infinity]);
newCluster = {
name: 'Node ' + iter,
height: nearestPair[2],
indexes: clusters[nearestPair[0]].indexes.concat(clusters[nearestPair[1]].indexes),
children: [clusters[nearestPair[0]], clusters[nearestPair[1]]],
};
verbose && console.log(newCluster);
clustersGivenK.push(clusters.map(function (c) { return c.indexes; }));
// remove merged nodes and push new node
clusters.splice(Math.max(nearestPair[0], nearestPair[1]), 1);
clusters.splice(Math.min(nearestPair[0], nearestPair[1]), 1);
clusters.push(newCluster);
}
treeRoot = clusters[0];
// clust._rebalanceTree(treeRoot);
};
// TODO: better rebalancing algo? ... this is just for presentation
// rebalance after tree is built (b/c it is top down operation)
// clust._rebalanceTree = function(node) {
// if(node.parent && node.parent.children && node.parent.children.length &&
// node.children && node.children.length) {
// var rightDistance = clust['_'+linkage+'Distance'](
// node.parent.children[1].indexes,
// node.children[0].indexes);
// var leftDistance = clust['_'+linkage+'Distance'](
// node.parent.children[1].indexes,
// node.children[1].indexes);
// // switch order of node.children
// if(leftDistance > rightDistance) {
// node.children = [ node.children[1], node.children[0] ];
// node.indexes = node.children[0].indexes.concat(node.children[1].indexes);
// }
// }
// if(node.children) {
// clust._rebalanceTree(node.children[0]);
// clust._rebalanceTree(node.children[1]);
// }
// };
return clust;
}
\ No newline at end of file
import hcluster from './hcluster';
import * as d3 from 'https://cdn.jsdelivr.net/npm/d3@7/+esm';
// root config
export const getRootConfig = (config, chartData) => {
const normalizedData = () => {
let minmaxes = d3
.range(chartData[0].values.length)
.map((d) => [d3.min(chartData.map((el) => el.values[d])), d3.max(chartData.map((el) => el.values[d]))]);
return chartData.map((el) => ({
...el,
normalized: el.values.map((n, i) => (n - minmaxes[i][0]) / (minmaxes[i][1] - minmaxes[i][0]))
}));
};
const verticalData = () => {
return d3.range(chartData[0].values.length).map((d) => ({ normalized: normalizedData().map((el) => el.normalized[d]) }));
};
const clustering = hcluster().distance('angular').linkage('avg').posKey('normalized').data(normalizedData());
const verticalClustering = hcluster().distance('angular').linkage('avg').posKey('normalized').data(verticalData());
const tree = (data) => {
const root = d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height));
root.dx = config.radius;
root.dy = 0 / (root.height + 1);
return d3
.cluster()
.size([config.heatHeight, 0])
.separation(() => 2)(root);
};
const vertTree = (data) => {
const root = d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height));
root.dy = config.radius;
root.dx = 0 / (root.height + 1);
return d3
.cluster()
.size([config.heatWidth, 0])
.separation(() => 2)(root);
};
const root = tree(clustering.tree());
const vertRoot = vertTree(verticalClustering.tree());
const orderingx = vertRoot.leaves().map((el) => el.data.indexes[0]);
return {
root,
vertRoot,
orderingx
}
}
// color config
export const setColor = (min, max, color) => {
let colors = [
'#313695',
'#436FB1',
'#6BA2CB',
'#9BCCE2',
'#F0F9D8',
'#F0F9D8',
'#FEF0A9',
'#FDCD7E',
'#FA9C58',
'#EE613D',
'#D22B26',
'#A50026'
];
if (Array.isArray(color)) {
colors = !!color.length ? color : colors;
} else if (!!color) {
colors = color.split(',');
}
return () => d3.scaleQuantize().domain([min, max]).range(colors);
};
// maxnumber minNumber
export const setMaxMinNumber = (chartData, isInteger = true) => {
let maxNumArr = [],
minNumArr = [],
maxNumber = 0,
minNumber = 0;
for (let i = 0; i < chartData.length; i++) {
maxNumArr.push(d3.max(chartData[i].values));
minNumArr.push(d3.min(chartData[i].values));
}
// 最大值 最小值
maxNumber = isInteger ? Math.round(d3.max(maxNumArr)) : d3.max(maxNumArr);
minNumber = isInteger ? Math.round(d3.min(minNumArr)) : d3.min(minNumArr);
return {
maxNumber,
minNumber
}
}
// tips
export const setTips = (rect) => {
const tips = d3
.select('.tip')
.style('visibility', 'hidden')
.style('position', 'relative')
.style('background-color', 'rgba(0, 0, 0, .8)')
.style('color', '#fff')
.style('border-radius', '5px')
.style('width', '200px')
.style('text-align', 'center')
.style('height', '20px')
.style('padding', '5px');
rect.on('mouseover', (event, d) => {
tips.style('visibility', 'visible');
})
.on('mousemove', function (event, d) {
tips.style('visibility', 'visible')
.style('left', event.offsetX + 15 + 'px')
.style('top', event.offsetY + 50 + 'px')
.html(`${d}`);
})
.on('mouseleave', function () {
tips.style('visibility', 'hidden');
});
};
// heatmap
export const setHeatmap = (config) => {
return config.svg
.append('g')
.attr('transform', `translate(${config.position.heatmap[0]}, ${config.position.heatmap[1]})`)
.selectAll('g')
.data(config.root.leaves())
.join('g')
.attr('transform', (d, i) => `translate(0, ${config.radius * 2 * i})`)
.selectAll('rect')
.data((d) => d.data.values.map((e, i) => d.data.values[config.orderingx[i]]))
.join('rect')
.attr('x', (d, i) => i * 2 * config.radius)
.attr('y', 0)
.attr('width', 2 * config.radius)
.attr('height', 2 * config.radius)
.attr('fill', config.color());
};
// Basis heatmap
export const setBasisHeatmap = (config) => {
return config.svg
.append('g')
.attr('transform', `translate(${config.margin + 0}, ${config.margin})`)
.selectAll('g')
.data(config.root.leaves())
.join('g')
.attr('transform', (d, i) => `translate(0, ${130 + config.radius * 2 * i})`)
.selectAll('rect')
.data((d) => d.data.values.map((e, i) => d.data.values[config.orderingx[i]]))
.join('rect')
.attr('x', (d, i) => i * 2 * config.radius)
.attr('y', 0)
.attr('width', 2 * config.radius)
.attr('height', 2 * config.radius)
.attr('fill', config.color());
};
// Consensus heatmap
export const setConsensusHeatmap = (config) => {
return config.svg
.append('g')
.attr('transform', `translate(${config.margin + 0}, ${config.margin})`)
.selectAll('g')
.data(config.root.leaves())
.join('g')
.attr('transform', (d, i) => `translate(0, ${150 + config.radius * 2 * i})`)
.selectAll('rect')
.data((d) => d.data.values.map((e, i) => d.data.values[config.orderingx[i]]))
.join('rect')
.attr('x', (d, i) => i * 2 * config.radius)
.attr('y', 0)
.attr('width', 2 * config.radius)
.attr('height', 2 * config.radius)
.attr('fill', config.color());
};
// Silhouette heatmap
export const setSilhouetteHeatmap = (config) => {
return config.svg
.append('g')
.attr('transform', `translate(${config.margin + 0}, ${config.margin * 3})`)
.selectAll('g')
.data(config.root.leaves())
.join('g')
.attr('transform', (d, i) => `translate(0, ${150 + config.radius * 2 * i})`)
.selectAll('rect')
.data((d) => d.data.values.map((e, i) => d.data.values[config.orderingx[i]]))
.join('rect')
.attr('x', (d, i) => i * 2 * config.radius)
.attr('y', 0)
.attr('width', 2 * config.radius)
.attr('height', 2 * config.radius)
.attr('fill', d => d3.interpolateGreens(d));
};
// legend
export const setLegend = (config) => {
const legendData = [...Array.from({ length: config.maxNumber }, (item, i) => i)];
config.svg.append('g')
.attr('transform', `translate(${config.position.legend[0]}, ${config.position.legend[1]})`)
.selectAll()
.data(legendData)
.join('g')
.attr('transform', (d, i, nodes) => `translate(0, ${(nodes.length - i) * 18 - 18})`)
.call((g) => g.append('rect').attr('width', 18).attr('height', 18).attr('fill', config.color()))
.call((g) =>
g
.append('text')
.attr('x', 21)
.attr('y', 10)
.attr('dy', '0.3em')
.style('font', '13px sans-serif')
.text((d) => d)
);
};
// Basis legend
export const setBasisLegend = (config) => {
const legendData = [...Array.from({ length: config.maxNumber }, (item, i) => i)];
const textX = 0 + config.xAxisLength * (config.radius * 2) + 180
config.svg.append('text')
.attr('x', textX)
.attr('y', config.height / 3 - 110)
.attr('fill', '#333333')
.attr('font-size', '20px')
.attr('font-family', `${config.titleConfig.fontFamily}`)
.attr('font-style', 'normal')
.attr('font-weight', 'bold')
.style('text-anchor', 'middle')
.text('basis');
config.svg.append('g')
.selectAll()
.data(legendData)
.join('g')
.attr('transform', (d, i, nodes) => `translate(${0 + config.xAxisLength * (config.radius * 2) + 160}, ${config.height / 3 + (nodes.length / 2 - i - 1) * 21 - 50})`)
.call((g) => g.append('rect').attr('width', 15).attr('height', 15).attr('fill', config.color()))
.call((g) =>
g
.append('text')
.attr('x', 20)
.attr('y', 9)
.attr('dy', '0.3em')
.style('font', '13px sans-serif')
.text((d) => d)
);
}
// Consensus legend
export const setConsensusLegend = (config) => {
const legendData = [...Array.from({ length: config.maxNumber }, (item, i) => i)];
const textX = 0 + config.xAxisLength * (config.radius * 2) + 205
config.svg.append('text')
.attr('x', textX)
.attr('y', config.height / 3 + 50)
.attr('fill', '#333333')
.attr('font-size', '20px')
.attr('font-family', `${config.titleConfig.fontFamily}`)
.attr('font-style', 'normal')
.attr('font-weight', 'bold')
.style('text-anchor', 'middle')
.text('consensus');
config.svg.append('g')
.selectAll()
.data(legendData)
.join('g')
.attr('transform', (d, i, nodes) => `translate(${0 + config.xAxisLength * (config.radius * 2) + 160}, ${config.height / 3 + (nodes.length / 2 - i - 1) * 21 + 110})`)
.call((g) => g.append('rect').attr('width', 15).attr('height', 15).attr('fill', config.color()))
.call((g) =>
g
.append('text')
.attr('x', 20)
.attr('y', 9)
.attr('dy', '0.3em')
.style('font', '13px sans-serif')
.text((d) => d)
);
}
// Silhouette legend
export const setSilhouetteLegend = (config) => {
const textX = 0 + config.xAxisLength * (config.radius * 2) + 200
const legendData = [config.maxNumber, config.minNumber]
config.svg.append('text')
.attr('x', textX)
.attr('y', config.height / 3 + 210)
.attr('fill', '#333333')
.attr('font-size', '20px')
.attr('font-family', `${config.titleConfig.fontFamily}`)
.attr('font-style', 'normal')
.attr('font-weight', 'bold')
.style('text-anchor', 'middle')
.text('silhouette');
config.svg.append('g')
.selectAll()
.data(legendData)
.join('text')
.attr('x', textX + 20)
.attr('y', (d, i) => config.height / 3 + i * 40 + 240)
.attr('fill', config.titleConfig.color)
.attr('font-style', `${config.titleConfig.fontStyle}`)
.attr('font-weight', `${config.titleConfig.fontWeight}`)
.style('font', '13px sans-serif')
.style('text-anchor', 'middle')
.text((d) => d);
const gradient = config.svg
.append('defs')
.append('linearGradient')
.attr('id', 'mylLnearGradient')
.attr('x1', '0%')
.attr('y1', '0%')
.attr('x2', '0%')
.attr('y2', '100%')
.attr('spreadMethod', 'pad');
gradient
.append('stop')
.attr('offset', '0%')
.attr('stop-color', 'rgb(0,128,0,1)')
.attr('stop-opacity', 1);
gradient
.append('stop')
.attr('offset', '100%')
.attr('stop-color', 'rgb(0,128,0,0.1)')
.attr('stop-opacity', 1);
config.svg.append('rect')
.attr('width', '15')
.attr('height', '60')
.attr("fill", "url(#mylLnearGradient)")
.attr("x", `${0 + config.xAxisLength * (config.radius * 2) + 160}`)
.attr("y", `${config.height / 3 + 225}`)
}
// 标签
export const setAxisTitle = (config) => {
const labelList = config.root.leaves().map((item) => item.data.id);
config.svg.append('g')
.attr('transform', `translate(${config.position.axisLabel[0]}, ${config.position.axisLabel[1]})`)
.selectAll()
.data(labelList)
.join('g')
.attr('transform', (d, i) => `translate(0,${i * 2 * config.radius + 3})`)
.call((g) =>
g
.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('dy', '0.35em')
.style('font', `6px sans-serif`)
.style('fill', `#000`)
.text((d) => d)
);
};
// 标题
export const setLabel = (config) => {
config.svg.append('text')
.attr('x', config.position.title[0])
.attr('y', config.position.title[1])
.attr('fill', config.titleConfig.color)
.attr('font-size', `${config.titleConfig.fontSize}px`)
.attr('font-family', `${config.titleConfig.fontFamily}`)
.attr('font-style', `${config.titleConfig.fontStyle}`)
.attr('font-weight', `${config.titleConfig.fontWeight}`)
.style('text-anchor', 'middle')
.text(config.title);
};
......@@ -16,10 +16,11 @@ import MsDimplot from '../../components/MsDimplot/index.vue';
import BeeColonyDiagram from '../../components/BeeColonyDiagram/index.vue';
import CorrelationAnalysis from '../../components/CorrelationAnalysis/index.vue';
import ClusterHeatmap from '../../components/ClusterHeatmap/index.vue';
import ClusterHeatmapSecond from '../../components/ClusterHeatmapSecond/index.vue';
import CircularHeatmap from '../../components/CircularHeatmap/index.vue';
import Test from '../../components/Test/index.vue';
const chartCurrent = ref(6);
const chartCurrent = ref(7);
const chartList = shallowRef([
{ title: '线性拟合图', value: 1, comName: LinearFitting },
......@@ -28,7 +29,8 @@ const chartList = shallowRef([
{ title: '蜂群图', value: 4, comName: BeeColonyDiagram },
{ title: '相关性分析', value: 5, comName: CorrelationAnalysis },
{ title: '聚类热图', value: 6, comName: ClusterHeatmap },
{ title: '环状热图', value: 7, comName: CircularHeatmap },
{ title: '聚类热图-2', value: 7, comName: ClusterHeatmapSecond },
{ title: '环状热图', value: 8, comName: CircularHeatmap },
{ title: 'test', value: 0, comName: Test }
]);
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论