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

feat: 线性拟合图

上级 693ce04b
差异被折叠。
......@@ -10,6 +10,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@observablehq/plot": "^0.6.11",
"anychart": "^8.11.1",
"axios": "^1.4.0",
"d3": "^4.5.0",
......
<template>
<div ref="chartRef"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as Plot from '@observablehq/plot';
// import * as d3 from 'd3';
import { chartData } from '../mock';
import PubSub from 'pubsub-js';
// 根节点实例
const chartRef = ref(null);
// 配置项数据
const options = ref({});
// pubsub实例
const pubSubInstance = ref(null);
// plot实例
const plotInstance = ref(null);
// 生成图表
const generateChart = (data) => {
const ret = options.value;
// console.log(ret);
// 点,线,阴影颜色设置
let point_color = '#FF5733';
let line_color = '#39FDBE';
let shadow_color = '#D6D6D6';
if (ret.data_color_list?.length) {
point_color = ret.data_color_list[0];
line_color = ret.data_color_list[1];
shadow_color = ret.data_color_list[2];
}
plotInstance.value = Plot.plot({
width: ret.chart_width || 800,
height: ret.chart_height || 600,
marginTop: ret.margin_top || 60,
marginRight: ret.margin_right || 60,
marginBottom: ret.margin_bottom || 60,
marginLeft: ret.margin_left || 60,
color: {
legend: ret.show_legend
},
x: {
grid: ret.show_x_gridLine
},
y: {
grid: ret.show_y_gridLine
},
marks: [
Plot.ruleX([0]),
Plot.ruleY([-1]),
Plot.axisX({
label: !!ret.x_title ? ret.x_title : null,
stroke: ret.x_tickMarks_color || 'currentColor',
tickSize: ret.show_x_tickMarks ? ret.x_tickMarks_length : 0,
fill: ret.xtitle_font_color || 'currentColor',
fontSize: ret.xtitle_font_size || 16,
fontStyle: ret.xtitle_font_style ? 'oblique' : 'normal',
fontFamily: ret.xtitle_font_family || 'Arial',
fontWeight: ret.xtitle_font_weight ? 'bold' : 'normal'
}),
Plot.axisY({
label: !!ret.y_title ? ret.y_title : null,
stroke: ret.y_tickMarks_color || 'currentColor',
tickSize: ret.show_x_tickMarks ? ret.y_tickMarks_length : 0,
fill: ret.ytitle_font_color || 'currentColor',
fontSize: ret.ytitle_font_size || 16,
fontStyle: ret.ytitle_font_style ? 'oblique' : 'normal',
fontFamily: ret.ytitle_font_family || 'Arial',
fontWeight: ret.ytitle_font_weight ? 'bold' : 'normal'
}),
Plot.dot(data, { x: 'x', y: 'y', fill: point_color, r: ret.point_radius || 5 }),
Plot.linearRegressionY(data, {
x: 'x',
y: 'y',
stroke: line_color,
fill: shadow_color,
fillOpacity: ret.shadow_opacity || 0.5
}),
Plot.text([ret.title || 'y=3.05+0.371x, R²=0.129'], {
x: 1,
fill: ret.title_font_color || 'currentColor',
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',
frameAnchor: 'top',
textAnchor: 'start'
})
]
});
chartRef.value.appendChild(plotInstance.value);
};
onMounted(() => {
pubSubInstance.value = PubSub.subscribe('options', (m, data) => {
options.value = data;
chartRef.value.removeChild(plotInstance.value);
generateChart(chartData);
});
generateChart(chartData);
});
onUnmounted(() => {
PubSub.unsubscribe(pubSubInstance.value);
});
</script>
<style scoped></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>
<div class="form-item">
<span>上边距:</span>
<input type="text" v-model="dynamic_parameter.margin_top" />
</div>
<div class="form-item">
<span>右边距:</span>
<input type="text" v-model="dynamic_parameter.margin_right" />
</div>
<div class="form-item">
<span>下边距:</span>
<input type="text" v-model="dynamic_parameter.margin_bottom" />
</div>
<div class="form-item">
<span>左边距:</span>
<input type="text" v-model="dynamic_parameter.margin_left" />
</div>
<h3 class="type content">文本</h3>
<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">X轴配置</h3>
<div class="form-item">
<span>显示坐标轴:</span>
<input type="checkbox" v-model="dynamic_parameter.show_x_axis" />
</div>
<div class="form-item">
<span>显示刻度线:</span>
<input type="checkbox" v-model="dynamic_parameter.show_x_tickMarks" />
</div>
<div class="form-item">
<span>显示网格:</span>
<input type="checkbox" v-model="dynamic_parameter.show_x_gridLine" />
</div>
<div class="form-item">
<span>刻度线颜色:</span>
<input type="text" v-model="dynamic_parameter.x_tickMarks_color" />
</div>
<div class="form-item">
<span>刻度线长度:</span>
<input type="text" v-model="dynamic_parameter.x_tickMarks_length" />
</div>
<div class="form-item">
<span>字体:</span>
<input type="text" v-model="dynamic_parameter.xtitle_font_family" />
</div>
<div class="form-item">
<span>字号:</span>
<input type="text" v-model="dynamic_parameter.xtitle_font_size" />
</div>
<div class="form-item">
<span>字体颜色:</span>
<input type="text" v-model="dynamic_parameter.xtitle_font_color" />
</div>
<div class="form-item">
<span>字体加粗:</span>
<input type="checkbox" v-model="dynamic_parameter.xtitle_font_weight" />
</div>
<div class="form-item">
<span>斜体:</span>
<input type="checkbox" v-model="dynamic_parameter.xtitle_font_style" />
</div>
<div class="form-item">
<span>x轴标题:</span>
<input type="text" v-model="dynamic_parameter.x_title" />
</div>
<h3 class="type content">Y轴配置</h3>
<div class="form-item">
<span>显示坐标轴:</span>
<input type="checkbox" v-model="dynamic_parameter.show_y_axis" />
</div>
<div class="form-item">
<span>显示刻度线:</span>
<input type="checkbox" v-model="dynamic_parameter.show_y_tickMarks" />
</div>
<div class="form-item">
<span>显示网格:</span>
<input type="checkbox" v-model="dynamic_parameter.show_y_gridLine" />
</div>
<div class="form-item">
<span>刻度线颜色:</span>
<input type="text" v-model="dynamic_parameter.y_tickMarks_color" />
</div>
<div class="form-item">
<span>刻度线长度:</span>
<input type="text" v-model="dynamic_parameter.y_tickMarks_length" />
</div>
<div class="form-item">
<span>字体:</span>
<input type="text" v-model="dynamic_parameter.ytitle_font_family" />
</div>
<div class="form-item">
<span>字号:</span>
<input type="text" v-model="dynamic_parameter.ytitle_font_size" />
</div>
<div class="form-item">
<span>字体颜色:</span>
<input type="text" v-model="dynamic_parameter.ytitle_font_color" />
</div>
<div class="form-item">
<span>字体加粗:</span>
<input type="checkbox" v-model="dynamic_parameter.ytitle_font_weight" />
</div>
<div class="form-item">
<span>斜体:</span>
<input type="checkbox" v-model="dynamic_parameter.ytitle_font_style" />
</div>
<div class="form-item">
<span>x轴标题:</span>
<input type="text" v-model="dynamic_parameter.y_title" />
</div>
<h3 class="type content">图表配置</h3>
<div class="form-item">
<span>散点图半径:</span>
<input type="text" v-model="dynamic_parameter.point_radius" />
</div>
<div class="form-item">
<span>阴影透明度:</span>
<input type="text" v-model="dynamic_parameter.shadow_opacity" />
</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: 800,
chart_height: 600,
margin_top: 60,
margin_right: 60,
margin_bottom: 60,
margin_left: 60,
//标题配置
title: '',
title_font_family: 'Arial',
title_font_weight: true,
title_font_size: 20,
title_font_color: '#333333',
title_font_style: true,
//横轴标题
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: ['#FF5733', '#39FDBE', '#D6D6D6'],
point_radius: 5, // 散点图半径
shadow_opacity: 0.5, // 阴影透明度 0~1
//图例
show_legend: true,
legend_position: '1,1',
//坐标轴
show_x_axis: true,
show_x_line: true,
show_x_tickMarks: true,
show_x_gridLine: false,
show_x_title: true,
x_title: 'cell_ident',
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: 'percentage',
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>
export const chartData = [
{ "x": 6, "y": 1 },
{ "x": 7, "y": 5 },
{ "x": 6, "y": 6 },
{ "x": 9, "y": 5 },
{ "x": 7, "y": 4 },
{ "x": 7, "y": 10 },
{ "x": 10, "y": 3 },
{ "x": 10, "y": 9 },
{ "x": 1, "y": 1 },
{ "x": 1, "y": 7 },
{ "x": 5, "y": 8 },
{ "x": 8, "y": 9 },
{ "x": 2, "y": 1 }
]
\ No newline at end of file
import * as d3 from 'd3';
export function linearRegression(data) {
let n = data.length, sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (let i = 0; i < data.length; ++i) {
const xi = data[i].x;
const yi = data[i].y;
if (!Number.isFinite(xi) || !Number.isFinite(yi)) { --n; continue; }
sumX += xi;
sumY += yi;
sumXY += xi * yi;
sumX2 += xi * xi;
}
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
return x => slope * x + intercept;
}
export function confidenceInterval(data, predict) {
const mean = d3.sum(data, (d) => d.x) / data.length;
let a = 0,
b = 0;
for (let i = 0; i < data.length; ++i) {
a += Math.pow(data[i].x - mean, 2);
b += Math.pow(data[i].y - predict(data[i].x), 2);
}
const sy = Math.sqrt(b / (data.length - 2));
const t = inverseT(+0.05, data.length - 2);
return function (x) {
const Y = predict(x);
const se = sy * Math.sqrt(1 / data.length + Math.pow(x - mean, 2) / a);
return { x, left: Y - t * se, right: Y + t * se };
};
}
export function normdev(p) {
if (p < 0 || p > 1) return false;
if (p == 0) return -Infinity;
if (p == 1) return Infinity;
let a0 = 3.3871328727963666080e+0,
a1 = 1.3314166789178437745e+2,
a2 = 1.9715909503065514427e+3,
a3 = 1.3731693765509461125e+4,
a4 = 4.5921953931549871457e+4,
a5 = 6.7265770927008700853e+4,
a6 = 3.3430575583588128105e+4,
a7 = 2.5090809287301226727e+3,
b1 = 4.2313330701600911252e+1,
b2 = 6.8718700749205790830e+2,
b3 = 5.3941960214247511077e+3,
b4 = 2.1213794301586595867e+4,
b5 = 3.9307895800092710610e+4,
b6 = 2.8729085735721942674e+4,
b7 = 5.2264952788528545610e+3,
c0 = 1.42343711074968357734e+0,
c1 = 4.63033784615654529590e+0,
c2 = 5.76949722146069140550e+0,
c3 = 3.64784832476320460504e+0,
c4 = 1.27045825245236838258e+0,
c5 = 2.41780725177450611770e-1,
c6 = 2.27238449892691845833e-2,
c7 = 7.74545014278341407640e-4,
d1 = 2.05319162663775882187e+0,
d2 = 1.67638483018380384940e+0,
d3 = 6.89767334985100004550e-1,
d4 = 1.48103976427480074590e-1,
d5 = 1.51986665636164571966e-2,
d6 = 5.47593808499534494600e-4,
d7 = 1.05075007164441684324e-9,
e0 = 6.65790464350110377720e+0,
e1 = 5.46378491116411436990e+0,
e2 = 1.78482653991729133580e+0,
e3 = 2.96560571828504891230e-1,
e4 = 2.65321895265761230930e-2,
e5 = 1.24266094738807843860e-3,
e6 = 2.71155556874348757815e-5,
e7 = 2.01033439929228813265e-7,
f1 = 5.99832206555887937690e-1,
f2 = 1.36929880922735805310e-1,
f3 = 1.48753612908506148525e-2,
f4 = 7.86869131145613259100e-4,
f5 = 1.84631831751005468180e-5,
f6 = 1.42151175831644588870e-7,
f7 = 2.04426310338993978564e-15;
let q = p - 0.5, r, z;
// p close to 0.5
if (Math.abs(q) <= 0.425) {
r = 0.180625 - q * q;
z = q * (((((((a7 * r + a6) * r + a5) * r + a4) * r + a3) * r + a2) * r + a1) * r + a0)
/ (((((((b7 * r + b6) * r + b5) * r + b4) * r + b3) * r + b2) * r + b1) * r + 1);
return z;
}
if (q > 0) r = 1 - p;
else r = p;
r = Math.sqrt(-Math.log(r));
// p neither close to 0.5 nor 0 or 1
if (r <= 5) {
r += -1.6;
z = (((((((c7 * r + c6) * r + c5) * r + c4) * r + c3) * r + c2) * r + c1) * r + c0)
/ (((((((d7 * r + d6) * r + d5) * r + d4) * r + d3) * r + d2) * r + d1) * r + 1);
}
// p near 0 or 1
else {
r += -5;
z = (((((((e7 * r + e6) * r + e5) * r + e4) * r + e3) * r + e2) * r + e1) * r + e0)
/ (((((((f7 * r + f6) * r + f5) * r + f4) * r + f3) * r + f2) * r + f1) * r + 1);
}
if (q < 0.0) z = -z;
return z;
}
export function inverseT(p, df) {
const { sin, cos, sqrt, pow, exp, PI } = Math;
let a, b, c, d, t, x, y;
if (df == 1) return cos(p * PI / 2) / sin(p * PI / 2);
if (df == 2) return sqrt(2 / (p * (2 - p)) - 2);
a = 1 / (df - 0.5);
b = 48 / (a * a);
c = ((20700 * a / b - 98) * a - 16) * a + 96.36;
d = ((94.5 / (b + c) - 3) / b + 1) * sqrt(a * PI * 0.5) * df;
x = d * p;
y = pow(x, 2 / df);
if (y > 0.05 + a) {
// The procedure normdev(p) is assumed to return a negative normal
// deviate at the lower tail probability level p, e.g. -2.32 for p = 0.01.
x = normdev(p / 2);
y = x * x;
if (df < 5) c = c + 0.3 * (df - 4.5) * (x + 0.6);
c = (((0.05 * d * x - 5) * x - 7) * x - 2) * x + b + c;
y = (((((0.4 * y + 6.3) * y + 36) * y + 94.5) / c - y - 3) / b + 1) * x;
y = a * y * y;
if (y > 0.002) y = exp(y) - 1;
else y = 0.5 * y * y + y;
}
else {
y = ((1 / (((df + 6) / (df * y) - 0.089 * d - 0.822) * (df + 2) * 3) + 0.5 / (df + 4)) * y - 1) *
(df + 1) / (df + 2) + 1 / y;
}
return sqrt(df * y);
}
\ No newline at end of file
<template>
<div class="module_container">
dashboard
</div>
<div class="module_container">
<component :is="getDefaultShowChart()"></component>
</div>
</template>
<script setup>
import { ref, defineProps, watch, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, defineEmits } from 'vue';
const emit = defineEmits([])
const props = defineProps();
import { ref } from 'vue';
import LinearFitting from '../../components/LinearFitting/index.vue';
// 使用 watch 监听 props 的字段变化
watch(() => props, (newData, oldData) => {
});
//组件即将挂载
onBeforeMount(() => {
});
//组件已经挂载
onMounted(() => {
});
//组件即将更新
onBeforeUpdate(() => {
});
//组件已经更新
onUpdated(() => {
});
//组件即将卸载
onBeforeUnmount(() => {
});
//组件已经卸载
onUnmounted(() => {
});
const chartCurrent = ref(1);
const getDefaultShowChart = () => {
switch (chartCurrent.value) {
case 1:
return LinearFitting;
default:
break;
}
};
</script>
<style scoped></style>
\ No newline at end of file
<style scoped></style>
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论