文章目录
前言
这是一个学习笔记,仅供参考。
运行环境window10-VS2019-Dedug-x86。
安装了额外的库<graphics.h>。
一、了解graphics.h的基本用法
用graphics.h绘制出下面的图片
1.创建一个画板
#include <graphics.h> // 引用图形库头文件
#include <conio.h> // _getch()头文件
void TODO();
int main()
{
initgraph(640, 480); // 创建绘图窗口,大小为 640x480 像素
TODO(); // 创建完画板后做的事
_getch(); // 按任意键继续
closegraph(); // 关闭绘图窗口
return 0;
}
void TODO()
{
//TODO
}
2.设置白色背景
void TODO()
{
setbkcolor(WHITE); // 设置背景色为白色
cleardevice(); // 用背景色清空屏幕
}
3.绘制一个点
void TODO()
{
setlinecolor(RED); // 设置边框颜色为红色
setfillcolor(RED); // 设置填充颜色为红色
setfillstyle(BS_SOLID); // 设置填充样式为固实填充
fillcircle(100, 100, 5); // 绘制Point(100,100)半径为5px的点
}
4.绘制一条直线
void TODO()
{
setlinecolor(GREEN); // 设置线的颜色为绿色
setlinestyle(PS_SOLID, 3); // 设置线的样式为实线3px
line(100,300,500,300); // 绘制Point(100,300)到Point(500,300)的直线
}
5.绘制一段文字
void TODO()
{
RECT r = { 0, 0, 639, 479 }; // 矩形Point(0,0)到Point(639,479)
settextcolor(BLUE); // 设置文本颜色为蓝色
settextstyle(20, 0, _T("宋体")); // 设置字体高度为20px,宽自适应,字体为宋体
drawtext(_T("123"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 绘制"123"文本到矩形中,水平居中|垂直居中|限定一行
}
二、封装为graph2d类
基本了解graphics.h,接下来封装一个graph2d类,方便后续的一些操作。
在graph2d.h中包含的库、结构体、宏定义。如下:
#include <iostream>
#include <graphics.h>
#include <conio.h>
#include <vector>
#define f(_lambda) ([](double x) {return (_lambda); })
typedef struct mypoint {
double x;
double y;
}Point;
1.构造和析构
创建一个画板及结束后关闭画板
graph2d::graph2d(double _width, double _height)
{
width = _width;
height = _height;
initgraph(int(width), int(height), SHOWCONSOLE);
}
graph2d::~graph2d()
{
closegraph();
}
2.显示画板
#include <iostream>
#include "graph2d.h"
using namespace std;
int main()
{
graph2d g2d;
return 0;
}
执行后发现画板一闪而过,原因是没有等待,所以写一个等待函数。
void graph2d::waitKey(int _delay)
{
_getch();
}
加上等待函数画板就能一直显示了。
#include <iostream>
#include "graph2d.h"
using namespace std;
int main()
{
graph2d g2d;
g2d.waitKey();
return 0;
}
设置一下画板背景和一个矩形就能做到下图所示:
3.绘制坐标轴
具体代码省略,大致内容就是在画板上画画。如下:
void graph2d::initAxis()
{
setBackgroundColor(); // 设置灰色背景颜色
setAxisColor(); // 设置白色坐标轴
drawGrid(); // 绘制网格线
drawAxisX(); // 绘制x轴的坐标
drawAxisY(); // 绘制y轴的坐标
}
4.plot函数
plot函数是本次设计的主要目标,设想了3个重载,分别绘制点,线,方程。因为画板的坐标和绘制方程的坐标不一样,所以需要设计一个坐标转换的函数。
/*绘制一个点,参数为点的位置(无默认值),颜色(默认=RED),大小(默认=3),样式(默认=BS_SOLID)*/
void graph2d::plot(Point _point, COLORREF _color, int _size, int _type)
{
if (isBorder(_point)) {
setlinecolor(_color);
setfillcolor(_color);
setfillstyle(_type);
fillcircle(numConversion(fucCSDataToAbsCSData(_point).x, 'x'), numConversion(fucCSDataToAbsCSData(_point).y, 'y'), _size);
}
else {
showError("point");
}
}
/*绘制一连串的线,参数为一连串的点(无默认值),颜色(默认=BLACK),粗细(默认=3),样式(默认=PS_SOLID)*/
void graph2d::plot(std::vector<Point> _point, COLORREF _color, int _thickness, int _type)
{
setlinecolor(_color);
setlinestyle(_type, _thickness);
for (int i = 1; i < _point.size(); i++) {
if (isBorder(_point[i - 1]) && isBorder(_point[i])) {
line(numConversion(fucCSDataToAbsCSData(_point[i - 1]).x, 'x'), numConversion(fucCSDataToAbsCSData(_point[i - 1]).y, 'y'), numConversion(fucCSDataToAbsCSData(_point[i]).x, 'x'), numConversion(fucCSDataToAbsCSData(_point[i]).y, 'y'));
}
else {
showError("line");
}
}
}
/*绘制一个方程,参数为开始值(无默认值),结束值(无默认值),方程(无默认值),间隔(默认=0.1),颜色(默认=BLACK),粗细(默认=3),样式(默认=PS_SOLID)*/
void graph2d::plot(double _start, double _end, double (*_f)(double), double _step, COLORREF _color, int _thickness, int _type)
{
std::vector<Point> _point;
for (double i = _start; i <= _end; i += _step) {
_point.push_back({ i, _f(i) });
}
plot(_point, _color, _thickness, _type);
}
plot中用到的其他函数
/*判断点是否在坐标轴内*/
bool graph2d::isBorder(Point _point)
{
return (pointlb.x <= _point.x && _point.x <= pointrt.x && pointlb.y <= _point.y && _point.y <= pointrt.y);
}
/*y轴上下颠倒,并转换为整形*/
int graph2d::numConversion(double _num, char _axis)
{
if (_axis == 'x' || _axis == 'X') {
return int(_num);
}
if (_axis == 'y' || _axis == 'Y') {
return int(height - _num);
}
return 0;
}
/*方程坐标转换到画板的坐标*/
Point graph2d::fucCSDataToAbsCSData(Point _point)
{
return { ((_point.x - pointlb.x) / (pointrt.x - pointlb.x) * 0.78 * width + 0.12 * width),((_point.y - pointlb.y) / (pointrt.y - pointlb.y) * 0.78 * height + 0.1 * height) };
}
/*如果有一个点/线没有显示出来,则打印该函数*/
void graph2d::showError(std::string _err)
{
std::cout << "Maybe a " << _err << " is not in the coordinates" << std::endl;
}
5.其他函数
在设计时还设想了以下函数,代码略。
void title(std::string _str); // 标题
void xlabel(std::string _str); // x轴文本注释
void ylabel(std::string _str); // y轴文本注释,可以用空格换行
void text(std::string _str); // 在坐标中添加注释
void legend(std::string _str, COLORREF _color, int _num);// 标签
三、使用graph2d.h
1.使用示例
写好了graph2d.h和graph2d.cpp,测试一下代码。
1.1画一堆离散的点
代码:
#include <iostream>
#include <math.h>
#include <time.h>
#include "graph2d.h"
using namespace std;
double generateGaussianNoise(double mu, double sigma); // 产生高斯分布的值
double mrand(double a, double b); // 产生a-b的平均分布的随机数
int main()
{
srand(time(NULL));
graph2d g2d(700, 590, { -2,-2 }, { 2,2 });
g2d.xlabel("x轴");
g2d.ylabel("y 轴");
g2d.title("离散的点");
for (int i = 0; i < 1000; i++) { // 产生1000个点
double r = min(abs(generateGaussianNoise(0, 1)), 2); // 产生r的半径,服从高斯分布
double x = mrand(-sqrt(r), sqrt(r));
double y = mrand(-sqrt(r - x * x), sqrt(r - x * x));
g2d.plot({ x,y }); // 在r半径的圆内,随机产生一点
}
g2d.waitKey();
return 0;
}
double mrand(double a, double b)
{
return (rand() / double(RAND_MAX)) * (max(a, b) - min(a, b)) + min(a, b);
}
double generateGaussianNoise(double mu, double sigma)
{
const double epsilon = 1e-8;
const double two_pi = 2.0 * 3.14159265358979323846;
static double z0, z1;
static bool generate;
generate = !generate;
if (!generate)
return z1 * sigma + mu;
double u1, u2;
do
{
u1 = rand() * (1.0 / RAND_MAX);
u2 = rand() * (1.0 / RAND_MAX);
} while (u1 <= epsilon);
z0 = sqrt(-2.0 * log(u1)) * cos(two_pi * u2);
z1 = sqrt(-2.0 * log(u1)) * sin(two_pi * u2);
return z0 * sigma + mu;
}
截图:
1.2画一个五角星
代码:
#include <iostream>
#include "graph2d.h"
using namespace std;
int main()
{
graph2d g2d(700, 590, { 0,0 }, { 10,10 });
g2d.xlabel("x轴");
g2d.ylabel("y 轴");
g2d.title("五角星");
g2d.plot({ {2,0},{4.5,10},{7,0},{0,6},{9,6},{2,0} }, BLUE);
g2d.waitKey();
return 0;
}
截图:
1.3画一个方程
代码:
#include <iostream>
#include "graph2d.h"
using namespace std;
int main()
{
graph2d g2d(700, 590, { -10,-1 }, { 10,1 });
g2d.xlabel("x轴");
g2d.ylabel("y 轴");
g2d.title("sin(x)");
g2d.plot(-10, 10, f(sin(x)), 0.1, GREEN);
g2d.waitKey();
return 0;
}
截图:
2.具体代码
2.1graph2d.h
#pragma once
#include <iostream>
#include <graphics.h>
#include <conio.h>
#include <vector>
#include <math.h>
#define f(_lambda) ([](double x) {return (_lambda); })
typedef struct mypoint {
double x;
double y;
}Point;
class graph2d
{
private:
double height; // 画板高度
double width; // 画板宽度
Point pointlb; // 坐标轴左下角的点
Point pointrt; // 坐标轴右上角的点
int x_len; // x轴字的宽度
int y_len; // y轴字的宽度
public:
/*********************************
*
* 初始化、坐标转换、绘制坐标等等
*
*********************************/
graph2d(); // 初始化为默认值
graph2d(double _width, double _height); // 初始化画板宽高
graph2d(double _width, double _height, Point _pointlb, Point _pointrt); // 初始化画板宽高及网格
~graph2d(); // 析构函数
void waitKey(int _delay = 0); // 等待关闭
private:
/*********************************
*
* 坐标转换、绘制坐标等等
*
*********************************/
wchar_t* ctow(const char* str); // char* to wchar_t*
void setlen(int _len = 3); // 设置坐标文本长度
std::string dtos(double _num, char _axis); // string to double
void setGrid(Point _pointlb, Point _pointrt); // 设置网格
void drawGrid(); // 绘制网格
void drawAxisX(); // 绘制X轴
void drawAxisY(); // 绘制Y轴
bool isBorder(Point _point); // 是否在边界内
int numConversion(double _num, char _axis); // 将y轴颠倒
Point fucCSDataToAbsCSData(Point _point); // 方程的点转换到画板的点
Point absCSDataToFucCSData(Point _point); // 画板的点转换到方程的点
void showError(std::string _err); // 显示错误
void drawRectangle(Point _pointlb, Point _pointrt, COLORREF _colorl, COLORREF _colorf, int _style = BS_SOLID); //画正方形
void setBackgroundColor(COLORREF _color = 0xEAEAEA); // 设置画板背景颜色
void setAxisColor(); // 设置坐标系背景颜色
void initAxis(); // 初始化坐标轴内的信息
void mouseClick(); // 鼠标点击来获取该点函数坐标
public:
/*********************************
*
* 绘制坐标方程函数
*
*********************************/
void plot(Point _point, COLORREF _color = RED, int _size = 3, int _type = BS_SOLID); // 绘制点
void plot(std::vector<Point> _point, COLORREF _color = BLACK, int _thickness = 3, int _type = PS_SOLID); // 绘制一连串的线
void plot(double _start, double _end, double (*_f)(double), double _step = 0.1, COLORREF _color = BLACK, int _thickness = 3, int _type = PS_SOLID); // 绘制f(x)方程
void title(std::string _str); // 标题
void xlabel(std::string _str); // x轴文本注释
void ylabel(std::string _str); // y轴文本注释,可以用空格换行
void text(std::string _str); // 在坐标中添加注释
void legend(std::string _str, COLORREF _color, int _num);// 标签
};
2.2graph2d.cpp
#include "graph2d.h"
graph2d::graph2d()
{
width = 700;
height = 590;
initgraph(int(width), int(height), SHOWCONSOLE);
setGrid({ 0,0 }, { 10,10 });
setlen();
initAxis();
}
graph2d::graph2d(double _width, double _height)
{
width = _width;
height = _height;
initgraph(int(width), int(height), SHOWCONSOLE);
setGrid({ 0,0 }, { 10,10 });
setlen();
initAxis();
}
graph2d::graph2d(double _width, double _height, Point _pointlb, Point _pointrt)
{
width = _width;
height = _height;
initgraph(int(width), int(height), SHOWCONSOLE);
setGrid(_pointlb, _pointrt);
setlen();
initAxis();
}
graph2d::~graph2d()
{
closegraph();
}
wchar_t* graph2d::ctow(const char* str)
{
int length = strlen(str) + 1;
wchar_t* t = (wchar_t*)malloc(sizeof(wchar_t) * length);
memset(t, 0, length * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, str, strlen(str), t, length);
return t;
}
void graph2d::setlen(int _len)
{
x_len = max(int(1 + log10(max(pointlb.x, pointrt.x))), _len);
y_len = max(int(1 + log10(max(pointlb.y, pointrt.y))), _len);
}
std::string graph2d::dtos(double _num, char _axis)
{
int _len = 3;
std::string _str = ""; // 储存结果
int _lg = 0; // 10^__lg
if (_axis == 'x' || _axis == 'X')
_len = x_len;
if (_axis == 'y' || _axis == 'Y')
_len = y_len;
if (int(_num * pow(10.0, _len - 1)) == 0) {
for (int i = 0; i < _len - 1; i++) {
_str += '0';
}
return "0." + _str;
}
if (_num < 0) { _num = -_num; _str += '-'; }
else { _str += ' '; }
while (int(_num) > 1) { _num /= 10; _lg++; }
while (int(_num) < 1 && _lg > 0) { _num *= 10; _lg--; }
for (int i = 0; i < _len; i++) {
_str += ('0' + int(_num) % 10);
if (i == _lg && i + 1 != _len) {
_str += '.';
}
_num *= 10;
}
return _str;
}
void graph2d::waitKey(int _delay)
{
_getch();
}
void graph2d::setGrid(Point _pointlb, Point _pointrt)
{
pointlb = { min(_pointlb.x,_pointrt.x), min(_pointlb.y,_pointrt.y) };
pointrt = { max(_pointlb.x,_pointrt.x), max(_pointlb.y,_pointrt.y) };
}
void graph2d::drawGrid()
{
setlinecolor(0xE0E0E0);
setlinestyle(PS_SOLID, 1);
for (int i = 1; i < 10; i++) {
line(numConversion(0.078 * width * i + 0.12 * width, 'x'), numConversion(0.1 * height, 'y'), numConversion(0.078 * width * i + 0.12 * width, 'x'), numConversion(0.88 * height, 'y'));
line(numConversion(0.12 * width, 'x'), numConversion(0.078 * height * i + 0.1 * height, 'y'), numConversion(0.9 * width, 'x'), numConversion(0.078 * height * i + 0.1 * height, 'y'));
}
}
void graph2d::drawAxisX()
{
std::string _str = " ";
for (int i = 0; i <= 10; i++) {
_str += dtos((pointrt.x - pointlb.x) / 10 * i + pointlb.x, 'x') + " ";
}
RECT r = { numConversion(0,'x'), numConversion(0.04 * height,'y'), numConversion(width,'x'), numConversion(0.11 * height,'y') };
settextcolor(BLACK);
settextstyle(int((24 - 2 * x_len) * (width / 700) * 0.9), 0, _T("宋体"));
drawtext(ctow(_str.c_str()), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void graph2d::drawAxisY()
{
std::string _str = "\n\n\n\n\n";
for (int i = 10; i >= 0; i--) {
_str += dtos((pointrt.y - pointlb.y) / 10 * i + pointlb.y, 'y') + "\n\n\n";
}
RECT r = { numConversion(0,'x'), numConversion(height,'y'), numConversion(0.11 * width,'x'), numConversion(0,'y') };
settextcolor(BLACK);
settextstyle(int(15 * height / 590), 0, _T("宋体"));
drawtext(ctow(_str.c_str()), &r, DT_NOCLIP | DT_RIGHT);
}
bool graph2d::isBorder(Point _point)
{
return (pointlb.x <= _point.x && _point.x <= pointrt.x && pointlb.y <= _point.y && _point.y <= pointrt.y);
}
int graph2d::numConversion(double _num, char _axis)
{
if (_axis == 'x' || _axis == 'X') {
return int(_num);
}
if (_axis == 'y' || _axis == 'Y') {
return int(height - _num);
}
return 0;
}
Point graph2d::fucCSDataToAbsCSData(Point _point)
{
return { ((_point.x - pointlb.x) / (pointrt.x - pointlb.x) * 0.78 * width + 0.12 * width),((_point.y - pointlb.y) / (pointrt.y - pointlb.y) * 0.78 * height + 0.1 * height) };
}
Point graph2d::absCSDataToFucCSData(Point _point)
{
return { ((_point.x - 0.12 * width) * (pointrt.x - pointlb.x) / 0.78 / width + pointlb.x),((_point.y - 0.1 * height) * (pointrt.y - pointlb.y) / 0.78 / height + pointlb.y) };
}
void graph2d::showError(std::string _err)
{
std::cout << "Maybe a " << _err << " is not in the coordinates" << std::endl;
}
void graph2d::setBackgroundColor(COLORREF _color)
{
setbkcolor(_color);
cleardevice();
}
void graph2d::setAxisColor()
{
drawRectangle({ 0.12 * width ,0.1 * height }, { 0.9 * width ,0.88 * height }, BLACK, WHITE);
}
void graph2d::drawRectangle(Point _pointlb, Point _pointrt, COLORREF _colorl, COLORREF _colorf, int _style)
{
setlinecolor(_colorl);
setfillcolor(_colorf);
setfillstyle(_style);
fillrectangle(numConversion(_pointlb.x, 'x'), numConversion(_pointrt.y, 'y'), numConversion(_pointrt.x, 'x'), numConversion(_pointlb.y, 'y'));
}
void graph2d::initAxis()
{
setBackgroundColor();
setAxisColor();
drawGrid();
drawAxisX();
drawAxisY();
}
void graph2d::plot(Point _point, COLORREF _color, int _size, int _type)
{
if (isBorder(_point)) {
setlinecolor(_color);
setfillcolor(_color);
setfillstyle(_type);
fillcircle(numConversion(fucCSDataToAbsCSData(_point).x, 'x'), numConversion(fucCSDataToAbsCSData(_point).y, 'y'), _size);
}
else {
showError("point");
}
}
void graph2d::plot(std::vector<Point> _point, COLORREF _color, int _thickness, int _type)
{
setlinecolor(_color);
setlinestyle(_type, _thickness);
for (int i = 1; i < _point.size(); i++) {
if (isBorder(_point[i - 1]) && isBorder(_point[i])) {
line(numConversion(fucCSDataToAbsCSData(_point[i - 1]).x, 'x'), numConversion(fucCSDataToAbsCSData(_point[i - 1]).y, 'y'), numConversion(fucCSDataToAbsCSData(_point[i]).x, 'x'), numConversion(fucCSDataToAbsCSData(_point[i]).y, 'y'));
}
else {
showError("line");
}
}
}
void graph2d::plot(double _start, double _end, double (*_f)(double), double _step, COLORREF _color, int _thickness, int _type)
{
std::vector<Point> _point;
for (double i = _start; i <= _end; i += _step) {
_point.push_back({ i, _f(i) });
}
plot(_point, _color, _thickness, _type);
}
void graph2d::title(std::string _str)
{
RECT r = { numConversion(0,'x'), numConversion(0.88 * height,'y'), numConversion(width,'x'), numConversion(height,'y') };
settextcolor(BLACK);
settextstyle(int(20 * height / 590), 0, _T("宋体"));
drawtext(ctow(_str.c_str()), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void graph2d::xlabel(std::string _str)
{
RECT r = { numConversion(0,'x'), numConversion(0,'y'), numConversion(width,'x'), numConversion(0.05 * height,'y') };
settextcolor(BLACK);
settextstyle(int(20 * height / 590), 0, _T("宋体"));
drawtext(ctow(_str.c_str()), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void graph2d::ylabel(std::string _str)
{
RECT r = { numConversion(0.01 * width,'x'), numConversion(0.6 * height,'y'), numConversion(0.05 * width,'x'), numConversion(0,'y') };
settextcolor(BLACK);
settextstyle(int(20 * width / 700), 0, _T("宋体"));
drawtext(ctow(_str.c_str()), &r, DT_NOCLIP | DT_LEFT | DT_WORDBREAK);
}
void graph2d::text(std::string _str)
{
//TODO
}
void graph2d::legend(std::string _str, COLORREF _color, int _num)
{
//TODO
}
void graph2d::mouseClick()
{
//TODO
}
总结
在查阅资料时发现c++好像可以调用matlab画图,但具体不太会操作o(TヘTo)。
如有错误欢迎指出。
参考:
【EasyX使用教程】https://docs.easyx.cn/zh-cn/tutorials.
【char转换成wchar_t】https://blog.csdn.net/zhxuan30/article/details/22692389.
【高斯分布随机数】https://www.cnblogs.com/tsingke/p/6194737.html.