C#自定义曲线绘图面板

news2024/11/15 1:31:38

一、实现功能

1、显示面板绘制。

2、拖动面板,X轴、Y轴都可以拖动。

3、显示面板缩放,放大或者缩小。

4、鼠标在面板中对应的XY轴数值。

5、自动生成的数据数组,曲线显示。

6、鼠标是否在曲线上检测。

二、界面

拖动面板

鼠标在曲线上识别

三、部分功能代码实现

1、图形面板

        /// <summary>
        /// 生成坐标系图片面板
        /// </summary>
        /// <returns></returns>
        public Bitmap DrawImage()
        {
            ImageBoardInit();

            return bitMap;
        }
        /// <summary>
        /// 创建绘图面板-显示坐标系
        /// </summary>
        private void ImageBoardInit()
        {
            //1、绘制X、Y坐标轴
            if ((int)height != 0 && (int)width != 0)  //做这个判断是因为在最小化的情况下width=0。
            {
                bitMap = new Bitmap((int)width, (int)height);  //根据给定的高度和宽度创建一个位图图像
            }            
            graphics = Graphics.FromImage(bitMap);    //从指定的 objBitmap 对象创建 objGraphics 对象 (即在objBitmap对象中画图)

            //根据给定颜色(LightGray)填充图像的矩形区域 (背景)
            graphics.DrawRectangle(new Pen(boardColor, 1), 0, 0, width - 1, height - 1);    //画边框
            graphics.FillRectangle(new SolidBrush(backColor), 1, 1, width - 2, height - 2); //填充边框

            //画X轴,注意图像的原始X轴和Y轴计算是以左上角为原点,向右和向下计算的
            xAxisPoint1.X = xSpace + xSliceBegin;
            xAxisPoint1.Y = (height / 2) + ySliceBegin;    //
            xAxisPoint2.X = width;
            xAxisPoint2.Y = xAxisPoint1.Y;
            graphics.DrawLine(new Pen(new SolidBrush(axisColor), 2), xAxisPoint1, xAxisPoint2);

            //画Y轴
            yAxisPoint1.X = xSpace + xSliceBegin;
            yAxisPoint1.Y = height ;
            yAxisPoint2.X = xSpace + xSliceBegin;
            yAxisPoint2.Y = 0 ;
            graphics.DrawLine(new Pen(new SolidBrush(axisColor), 2), yAxisPoint1, yAxisPoint2);

            //2、面板标题
            //graphics.DrawString("曲线编辑器", new Font("宋体", fontSize), new SolidBrush(Color.Blue), new PointF(width / 2, ySpace / 2));

            //3、画X轴上刻度、刻度说明            
            xSlice = (width - xSpace ) / xSliceCount;
            ySlice = (height / 2) / (ySliceCount / 2);
            
            int tempCountX1 = (int)(- xSliceBegin / xSlice);
            int tempCountY1 = (int)Math.Abs((ySliceBegin / ySlice));
            tempCountX = tempCountX1 + xSliceCount;
            tempCountY = tempCountY1 + ySliceCount/2;
            //画网格虚线
            Pen penDashed = new Pen(new SolidBrush(Color.Black));
            penDashed.DashStyle = DashStyle.Dash;
            for (int i = 1; i < tempCountX + 1; i++)
            {
                //X轴刻度虚线
                graphics.DrawLine(penDashed, new PointF(i * xSlice + xSpace + xSliceBegin, 0 ), new PointF(i * xSlice + xSpace + xSliceBegin, height));
                //X轴刻度值标识文字
                string xStr = (i * xSliceValue).ToString();
                int nStrLength = xStr.Length;
                graphics.DrawString(xStr, new Font("宋体", fontSize), new SolidBrush(Color.Black), new PointF(i * xSlice + xSpace - fontSize * nStrLength + xSliceBegin, height / 2 + fontSize + ySliceBegin));
            }
            //Y轴虚线、刻度文字-正半轴
            for (int i = 0; i < tempCountY + 1; i++)
            {
                graphics.DrawLine(penDashed, new PointF(xSpace + xSliceBegin, height / 2 - i * ySlice + ySliceBegin), new PointF(width, height / 2 - i * ySlice + ySliceBegin));
                //Y轴刻度值标识文字
                string yStr = (i * ySliceValue).ToString();
                int nStrLength = yStr.Length;
                if(i>0)
                    graphics.DrawString(yStr, new Font("宋体", fontSize), new SolidBrush(Color.Black), new PointF(xSpace - fontSize * nStrLength + xSliceBegin, height / 2 - i * ySlice + ySliceBegin));
                
            }
            //Y轴虚线、刻度文字-负半轴
            for (int i = 1; i < tempCountY  + 1; i++)
            {
                graphics.DrawLine(penDashed, new PointF(xSpace + xSliceBegin, height / 2 + i * ySlice + ySliceBegin), new PointF(width, height / 2 + i * ySlice + ySliceBegin));
                //Y轴刻度值标识文字
                string yStr = (-i * ySliceValue).ToString();
                int nStrLength = yStr.Length;
                graphics.DrawString(yStr, new Font("宋体", fontSize), new SolidBrush(Color.Black), new PointF(xSpace - fontSize * nStrLength + xSliceBegin, height / 2 + i * ySlice + ySliceBegin));
            }
            //4、原点刻度说明
            graphics.DrawString("0", new Font("宋体", fontSize + 2), new SolidBrush(Color.Black), new PointF(xSpace - fontSize * 2 + xSliceBegin, height / 2 + ySliceBegin));
            
        }

2、数据点曲线显示

        /// <summary>
        /// 绘制XY轴曲线
        /// </summary>
        /// <param name="xDatas">X轴数据</param>
        /// <param name="yDatas">Y轴数据</param>
        /// <param name="splineColor">曲线颜色</param>
        /// <param name="tension">曲线系数:0.0f-1.0f,默认0.5f,0.0f是直线</param>
        /// <param name="isPointFill">是否标出点</param>
        public void DrawXY(float[] xDatas,float[] yDatas,Color splineColor,float tension,bool isPointFill)
        {
            int xLength = xDatas.Length;
            int yLength = yDatas.Length;
            if(xLength!=yLength)
            {
                graphics.DrawString("X和Y数据长度不相等",new Font("宋体", fontSize + 5), new SolidBrush(Color.Blue), new Point((int)xSpace, (int)(height / 2)));
                return;
            }
            PointF[] splinePoints = new PointF[xDatas.Length];
            float xSlicePos = 0, ySlicePos = 0; //转换后的点在Image面板上像素点位置
            Brush brushPoint = new SolidBrush(splineColor);        //数据点画刷
            for(int i=0;i<xDatas.Length;i++)
            {
                xSlicePos = xSliceBegin + xSpace + xDatas[i] * xSlice / xSliceValue;
                ySlicePos = ySliceBegin + height / 2 - yDatas[i] * ySlice / ySliceValue;
                splinePoints[i] = new PointF(xSlicePos, ySlicePos);
                if (isPointFill)
                {
                    //graphics.FillEllipse(brushPoint, xSlicePos - 4, ySlicePos - 4, 8, 8); //原点
                    graphics.FillRectangle(brushPoint, xSlicePos - 4, ySlicePos - 4, 8, 8); //矩形
                }                
            }
            graphics.DrawCurve(new Pen(splineColor, 2.0f), splinePoints, tension);
            //graphics.Dispose();
        }

3、鼠标当前所在面板位置对应的坐标系统XY值

        int ex = 0, ey = 0;
        int mouseToValueX = 0;
        float mouseToValueY = 0;   //鼠标位置当前值
        float fMouseToValueX = 0;
        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            ex = e.X;
            ey = e.Y;
            //1、显示鼠标在坐标系内X、Y对应的值
            fMouseToValueX =cureDraw.MousePosToValue_X(ex);
            mouseToValueX = (int)Math.Round(cureDraw.MousePosToValue_X(ex));
            //mouseToValueY = (float)Math.Round(cureDraw.MousePosToValue_Y(ey));
            mouseToValueY = cureDraw.MousePosToValue_Y(ey);
            if (mouseToValueX<0)
            {
                mouseToValueX = 0;
            }
            if (mouseToValueX >= 0 && ex < pictureBox1.Width - 60)
            {                
                labMousePos.Text = "X:" + mouseToValueX.ToString() + ";" + "Y:" + mouseToValueY.ToString();
                labMousePos.Location = new Point(ex + 50, ey + 40);
            }
            if (mouseToValueX >= 0 && ex > pictureBox1.Width - 60)
            {                
                labMousePos.Text = "X:" + mouseToValueX.ToString() + ";" + "Y:" + mouseToValueY.ToString();
                labMousePos.Location = new Point(ex - 50, ey + 40);
            }


        }

4、鼠标是否在曲线上检测

        bool isMouseOn = false;
        private void timer1_Tick(object sender, EventArgs e)
        {
            //bool isMouseOn = cureDraw.IsMouseOnPointCheck(xDatas, yDatas, ex, ey);
            //bool isMouseOn = cureDraw.IsMouseOnPointCheck(xDatasExpand, yDatasExpand, ex, ey);

            float tempX = 0, tempY = 0;
            int n = 0;
            bool b1 = false, b2 = false;
            foreach (var xData in xDatasExpand)
            {
                if(Math.Abs(xData-fMouseToValueX)<0.02)
                {
                    tempX = xData;
                }
            }
            for (int i = 0; i < xDatasExpand.Length;i++ )
            {
                if(xDatasExpand[i]==tempX)
                {
                    n = i;
                }
            }
            if (Math.Abs(yDatasExpand[n] - mouseToValueY)<2.5)
            {
                isMouseOn = true;
            }
            else
            {
                isMouseOn = false;
            }

            txtValue1.Text = xDatasExpand[n].ToString();    //Y
            txtValue2.Text = fMouseToValueX.ToString();

            txtValue3.Text = yDatasExpand[n].ToString();    //X
            txtValue4.Text = mouseToValueY.ToString();

            txtValue5.Text = n.ToString();

            if (isMouseOn)
            {
                labMousePos.ForeColor = Color.Red;                
            }
            else
            {
                labMousePos.ForeColor = Color.Black;
            }
        }

四、、工程下载链接

https://download.csdn.net/download/panjinliang066333/89775386

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2150387.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年亲测好用的四大在线翻译工具大盘点!

在互联网技术飞速发展的今天&#xff0c;各种在线翻译工具应运而生&#xff0c;它们不仅能够帮助我们跨越语言障碍&#xff0c;还能让我们更加便捷地获取世界各地的信息。接下来&#xff0c;我将结合自己的实际体验&#xff0c;为大家详细介绍几款优秀的在线翻译工具&#xff0…

15.多线程概述一(下篇)

目录 1.进程与线程 2.实现多线程方式一&#xff1a;继承Thread类【应用】 3.实现多线程方式二&#xff1a;实现Runnable接口【应用】 4.实现多线程方式三&#xff1a;实现Callable接口【应用】 5.三种实现方式的对比与套路 6.设置和获取线程名称/线程对象【应用】 7.线程优先级…

芹菜麦饭的做法

蒸煮时间&#xff1a; 芹菜麦饭的蒸煮时间因做法和食材的不同而有所差异&#xff0c;但一般在‌8到15分钟‌之间。具体蒸煮时间取决于芹菜的大小、切段的长度以及蒸锅的火力等因素。例如&#xff0c;将裹好面粉的芹菜段放入蒸锅&#xff0c;大火烧开后转中火蒸10~15分钟&#x…

LeetCode118:杨辉三角

题目链接&#xff1a;118. 杨辉三角 - 力扣&#xff08;LeetCode&#xff09; 代码如下 class Solution {public:vector<vector<int>> generate(int numRows) {vector<vector<int>> dp(numRows);vector<int> temp(numRows);for (int i 0; i &…

数据中台建设(六)—— 数据开发-提取数据价值

数据开发-提取数据价值 数据开发涉及的产品能力主要包括三部分&#xff1a;离线开发、实时开发和算法开发。 离线开发主要包括离线数据的加工、发布、运维管理&#xff0c;以及数据分析、数据探索、在线查询和及时分析相关工作。实时开发主要涉及数据的实时接入和实时处理。算…

Jmeter 线程组解析

1.seUp线程组 一种特殊的 threadGroup &#xff0c;可用于执行预测试操作&#xff1b;它的行为完全像一个正常的线程组元件&#xff0c;不同的是执行顺序。 它会在普通线程组执行之前被触发。 应用场景&#xff1a; 测试数据库操作功能时&#xff0c;用于执行打开数据库连接的…

邮件营销:助力企业转换客户,提升曝光率

邮件营销&#xff1a;独立站推广的关键策略 在独立站推广的众多方法中&#xff0c;邮件营销占据着重要地位。本文将为刚刚接触独立站运营的新手介绍邮件营销的基础知识。在信息泛滥的时代&#xff0c;开设一个店铺和成功地引流并不意味着一劳永逸。对于绝大多数中小型电商企业…

基于SpringBoot+Vue+MySQL的养老院管理系统

系统展示 管理员界面 家属界面 系统背景 随着全球人口老龄化的加速&#xff0c;养老院管理面临着前所未有的挑战。传统管理方式存在信息不透明、效率低下、资源分配不均等问题&#xff0c;难以满足日益增长的养老服务需求。因此&#xff0c;开发一套智能化、高效的养老院管理系…

Vue使用qrcodejs2-fix生成网页二维码

安装qrcodejs2-fix npm install qrcodejs2-fix核心代码 在指定父view中生成一个二维码通过id找到父布局 //通过id找到父布局let codeView document.getElementById("qrcode")new QRCode(codeView, {text: "测试",width: 128,height: 128,colorDark: #00…

三菱变频器Modbus-RTU 通讯规格

能够从变频器的 RS-485 端子使用 Modbus-RTU 通讯协议&#xff0c;进行通讯运行和参数设定。 NOTE: 1、使用 Modbus-RTU 通讯协议时&#xff0c;请设定Pr.549 协议选择 “1” 2、从主机按地址0(站号0)进行hodbus-RTU通讯时&#xff0c;为广播通讯&#xff0c;变频器不向主机发…

Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive

文章目录 一、创建Vue3工程1. vue-cli方式2. vite方式3. 项目小说明4. 安装插件&#xff1a;(1) Prettier--整理格式(2) Vue-official 二、 OptionsAPI 与 CompositionAPI1 选项式API的弊端2 组合式API的优势 三、setup1. 基本使用2 setup与组合式API3 setup语法糖 四、Vue中的…

Matlab R2024B软件安装教程

一、新版本特点 MATLAB R2024B版本带来了众多新特性和改进&#xff0c;旨在提升用户的内容创作体验和工程效率。以下是该版本的一些主要特点&#xff1a; 1. 性能提升和优化&#xff1a;R2024B版本在性能上进行了显著优化&#xff0c;无论是在提问、回答问题、发布新技巧还是…

OpenCV系列教程二:基本图像增强(数值运算)、滤波器(去噪、边缘检测)

文章目录 一、基本图像增强&#xff08;数值运算&#xff09;1.1 加法 &#xff08;cv2.add&#xff09;1.1.1 图像与标量相加&#xff08;调节亮度&#xff09;1.1.2 图像与图像相加&#xff08;两个图像shape要相同&#xff09;1.1.3 图像的加权加法&#xff08;渐变切换&…

nodejs 012:Babel(巴别塔)语言转换与代码兼容

这里写目录标题 安装 Babel配置presets配置&#xff1a;常见的 Babel Presetsplugins配置&#xff1a;以 plugin-transform-class-properties 的类中属性为例index.jsx Babel 是一个独立的 JavaScript 编译器&#xff0c;主要用于将现代 JavaScript 代码转换为旧版本的 JavaScr…

数据结构之二叉树(1)

数据结构之二叉树&#xff08;1&#xff09; 一、树 1、树的概念与结构 &#xff08;1&#xff09;树是一种非线性的数据结构&#xff0c;由n(n>0)个有限结点组成一个具有层次关系的集合。 &#xff08;2&#xff09;树有一个特殊的结点&#xff0c;叫做根结点&#xff…

Linux多块磁盘挂载同一目录

1、背景 由于服务器磁盘空间使用率已经使用占比超过95%&#xff0c;故购买了些磁盘对服务器进行扩容&#xff0c;但是如果直接进行磁盘挂载的&#xff0c;那么就只能挂载一个磁盘挂载一个目录&#xff0c;使用新磁盘空间得不到最大的利用率&#xff0c;故需要将多块磁盘合并为…

【RabbitMQ】死信队列、延迟队列

死信队列 死信&#xff0c;简单理解就是因为种种原因&#xff0c;无法被消费的消息。 有死信&#xff0c;自然就有死信队列。当一个消息在一个队列中变成死信消息之后&#xff0c;就会被重新发送到另一个交换器中&#xff0c;这个交换器就是DLX&#xff08;Dead Letter Excha…

sicp每日一题[1.1-1.29]

补一下之前的题目 Exercise 1.1 Below is a sequence of expressions. What is the result printed by the interpreter in response to each expression? Assume that the sequence is to be evaluated in the order in which it is presented. Exercise 1.2 Translate the …

【C++ Primer Plus习题】16.10

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> #include <string> #include <…

Vue2学习笔记(02条件渲染 、监视数据的原理)

1、v-if和v-show的区别 2、Vue监视数据的原理