Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Как в JawaScript Canvas нарисовать различные графики на разных участках числовой оси (https://javascript.ru/forum/misc/84376-kak-v-jawascript-canvas-narisovat-razlichnye-grafiki-na-raznykh-uchastkakh-chislovojj-osi.html)

IZUM 22.08.2022 17:47

Как в JawaScript Canvas нарисовать различные графики на разных участках числовой оси
 
Как в JawaScript Canvas нарисовать различные графики на разных участках числовой оси Х в Декартовой системе координат ?

IZUM 23.08.2022 18:27

Обычно, в JS на всей числовой оси X строиться график одной функции, например y(x) = x^2, на участке от 0 до 10, тоже например. А, надо построить графики нескольких функций на нескольких участках: y1(x) = x+1 на участке от 0 до 2; y2(x) = x^2+4 на участке от 2 до 5;y3(x) = x^2+2*x+1 на участке от 5 до 10; Фукции и интервалы выбраны совершенно произвольно.

IZUM 23.08.2022 19:37

Следует добавить, что это всё надо сделать на одной числовой оси Х

Alikberov 23.08.2022 20:25

<html><head>
<script>
function DrawFn() {
	var	ctx = document.querySelector("canvas").getContext("2d");
	var	drawer = ctx.moveTo.bind(ctx);
	var	i, x0, x1, x2, x, y, el, fn, range;
	ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
	for(i = 1; i <= 3; ++ i) {
		el = document.getElementById("Fn" + i);	// Обращаемся к одному из input-элементов
		fn = el.value.split(/:/)[1];		// Выражение функции справа от двоеточия
		range = el.value.split(/:/)[0].split("..");	// Интервал участка задаётся форматом "от..до" слева от двоеточия
		x1 = parseInt(range[0]);
		x2 = parseInt(range[1])
		ctx.strokeStyle = el.style.color;	// Цвет линии графика соответствует цвету input-текста
		ctx.beginPath();
		for(x0 = 0; x0 < ctx.canvas.width; ++ x0) {
			x = x0 / ctx.canvas.width * (x2 - x1) - x1;
			try {
				with(Math) {
					drawer(x0, ctx.canvas.height - eval(fn));
				}
			} catch(err) {
				break;
			}
			drawer = ctx.lineTo.bind(ctx);
		}
		ctx.stroke();
	}
}
</script>
</head>

<body onload='DrawFn()'>
Y1(x)=<input type=text id=Fn1 value='0..10:x**2' onchange='DrawFn()' style=color:magenta><br>
Y2(x)=<input type=text id=Fn1 value='0..2:x+1' onchange='DrawFn()' style=color:red><br>
Y3(x)=<input type=text id=Fn2 value='2..5:x**2+4' onchange='DrawFn()' style=color:orange><br>
Y4(x)=<input type=text id=Fn3 value='5..10:x**2+2*x+1' onchange='DrawFn()' style=color:blue><br>
<canvas width=800px height=600px></canvas>
</body>

IZUM 23.08.2022 21:56

Правильно ли, я понял, что графики выходят из отной точки - 0 ? Если, это так, то извините - это не совсем то, что требуется. В дествительности каждый из трёх графиков должен быть построен на указанном ему интервале на одной числовой оси Х

IZUM 23.08.2022 22:15

Вложений: 1
Думаю, надо добавить то, что это просто линии, так как не показаны координатные оси с разметкой и числовыми значениями по осям Х и Y

IZUM 23.08.2022 23:02

Высылаю файлы JS и HTML, где построен только один график. Может быть при использовании этих файлов можно решить мою проблему: надо построить графики нескольких функций на нескольких участках: y1(x) = x+1 на участке от 0 до 2; y2(x) = x^2+4 на участке от 2 до 5;y3(x) = x^2-2*x+1 на участке от 5 до 10; Функции и интервалы выбраны совершенно произвольно. а это всё надо сделать на одной числовой оси Х.

IZUM 23.08.2022 23:09

К сожалению, не получилось, или я не знаю, как в сообщение вложить
файлы JS и HTML
const canvasPlot = document.getElementById(`canvas_plot`);
const ctx = canvasPlot.getContext(`2d`);

//Рисуем сетку
const canvasPlotWidth = canvasPlot.clientWidth;
const canvasPlotHeight = canvasPlot.clientHeight;

const scaleX = 30; //расстояние между элементами сетки по Х
const scaleY = 30; //расстояние между элементами сетки по Y

//Рисуем главные оси
       // Обычное расположение осей     
//const xAxis = canvasPlotWidth / 2; 
//const yAxis = canvasPlotHeight / 2;
       // Уточнённое расположение осей за счёт сдвига осей
const xAxis = Math.round(canvasPlotWidth / scaleX / 2) * scaleX; 
const yAxis = Math.round(canvasPlotHeight / scaleY / 2) * scaleY;

// Выбор шрифта и его вид, размер

ctx.font = `${Math.round(scaleX / 2)}px Arial`;
ctx.textAlign = `left`;
ctx.textBaseline = `top`;

ctx.beginPath();
ctx.strokeStyle = `#f5f0e8`;


var zz = 5; //Зазор между осями и числами нумерации чисел

for (let i = 0; i <= canvasPlotWidth; i = i + scaleX) {
	ctx.moveTo(i, 0);
	ctx.lineTo(i, canvasPlotHeight); // нанесение вертикальных линий разметки
	
	ctx.fillText((i - xAxis) / scaleX, i + zz, yAxis + zz); //нанесение числовых значений на ось Х
}

for (let i = 0; i <= canvasPlotHeight; i = i + scaleY) {
	ctx.moveTo(0, i);
	ctx.lineTo(canvasPlotWidth, i); // нанесение горизонтальных линий разметки
	
	ctx.fillText((yAxis - i) / scaleY, xAxis + zz, i + zz); //нанесение числовых значений на ось Y
}
ctx.stroke();
ctx.closePath(); //закрыть путь, чтобы рисовать новые линии другим цветом


var zxy = 20; //Зазор между осями и обозначением этих осей  x,y 
ctx.beginPath();
ctx.strokeStyle = `#000000`;
	ctx.moveTo(xAxis, 0);
	ctx.lineTo(xAxis, canvasPlotHeight);
	ctx.fillText(`y`, xAxis - zxy, 0 ); // означение оси Y
	ctx.moveTo(0, yAxis);
	ctx.lineTo(canvasPlotWidth, yAxis);
	ctx.fillText(`x`, canvasPlotWidth - zxy, yAxis - zxy ); // означение оси X
ctx.stroke();
ctx.closePath(); //закрыть путь, чтобы рисовать новые линии другим цветом

//ПРЕОБРАЗОВАНИЕ CANVAS СИСТЕМУ КООРДИНАТ В ДЕКАРТОВУ СИСТЕМУ КООРДИНАТ
// Рисуем график функции
ctx.fillStyle = `#ff0000`; // цвет графика чёрный
//ctx.lineWidth = 0.5  //толщина линии
for (let i = 0; i <= canvasPlotWidth; i++) {
	const x = (i - xAxis) / scaleX;
	const y = Math.pow(x, 2); 
	ctx.fillRect(x * scaleX + xAxis, yAxis - scaleY * y, 4, 4);

IZUM 23.08.2022 23:14

<!DOCTYPE html> 
<html> 
<head> 
 <meta charset="utf-8"> 
 <link href="./Graf.css" rel="stylesheet">
</head> 
<body style= "margin": 0;> 
 <canvas id="canvas_plot" width="700" height="500"></canvas>
 <script src="./Graf.js"></script>
</body> 

</html>

IZUM 23.08.2022 23:16

Ссылка на график file:///C:/Users/%D0%98%D0%97%D0%9E/Desktop/JS1/Graf.html

Rise 23.08.2022 23:17

IZUM,
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Графики</title>
</head>
<body>

<canvas id="canvas" width="400" height="200" style="border:1px solid grey"></canvas>

<script>
function createPath(x1, x2, y, sx, sy) {
  const path = new Path2D();
  for (let x = x1; x <= x2; x++) {
    if (x == x1) {
      path.moveTo(x * sx, y(x) * sy);
    } else {
      path.lineTo(x * sx, y(x) * sy);
    }
  }
  return path;
}

const ctx = canvas.getContext('2d');

ctx.translate(0, 200);
ctx.scale(1, -1);

ctx.strokeStyle = 'red';
ctx.stroke(createPath(0,  2, x => x + 1, 30, 1));
ctx.strokeStyle = 'green';
ctx.stroke(createPath(2,  5, x => x ** 2 + 4, 30, 1));
ctx.strokeStyle = 'blue';
ctx.stroke(createPath(5, 10, x => x ** 2 + 2 * x + 1, 30, 1));
</script>

</body>
</html>

IZUM 23.08.2022 23:18

Вот ссылка на график file:///C:/Users/%D0%98%D0%97%D0%9E/Desktop/JS1/Graf.html

IZUM 23.08.2022 23:28

Rise,
Уважаемый Rise ! Спасибо. У Вас отлично получилось решение указанной проблемы с построением графиков. Но, почему то, не показаны чиловые оси X и Y. Это же, графики, не просто линии....

Alikberov 23.08.2022 23:43

<html><head>
<script>
function DrawFn() {
	var	ctx = document.querySelector("canvas").getContext("2d");
	var	width = ctx.canvas.width;
	var	height = ctx.canvas.height;
	var	size_x = width - 40;
	var	size_y = height - 24;
	var	drawer;
	var	i, x0, x1, x2, x, y, max_y, el, fn, range;
	var	els = document.getElementsByName("Fn_X");
	ctx.save();
	ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
	ctx.strokeStyle = "black";
	ctx.fillStyle = "black";
	ctx.beginPath();
	ctx.translate(32, 8);
	ctx.rect(0, 0, size_x, size_y);		// Черртим рамку
	for(i = 0; i <= 10; ++ i) {
		// Размечаем ось X
		ctx.moveTo(i * size_x / 10, 0);
		ctx.lineTo(i * size_x / 10, size_y);
		ctx.fillText(String(i), i * size_x / 10 - 4, size_y + 16);
		if(i & 1)
			continue;
		// Размечаем ось Y
		ctx.moveTo(0, i * size_y / 10);
		ctx.lineTo(size_x, i * size_y / 10);
		ctx.fillText(String(i * 10), -16, (10 - i) * size_y / 10 + 8);
	}
	ctx.stroke();
	// Очищаем основную област графика
	ctx.clearRect(8, 8, size_x - 16, size_y - 16);
	for(i = 0; i < els.length; ++ i) {
		ctx.strokeStyle = els[i].style.color;	// Цвет линии графика соответствует цвету input-текста
		fn = els[i].value.split(/:/)[1];		// Выражение функции справа от двоеточия
		range = els[i].value.split(/:/)[0].split("..");	// Интервал участка задаётся форматом "от..до" слева от двоеточия
		x1 = parseInt(range[0]);
		x2 = parseInt(range[1]);
		max_y = size_y;
		ctx.beginPath();
		drawer = ctx.moveTo.bind(ctx);
		for(x0 = size_x * x1 / 10; x0 < size_x * x2 / 10; ++ x0) {
			x = x0 / size_x * 10;
			try {
				with(Math) {
					drawer(x0, y = size_y - eval(fn) * size_y / 100);
					max_y = Math.min(max_y, y);
				}
			} catch(err) {
				break;
			}
			drawer = ctx.lineTo.bind(ctx);
		}
		ctx.stroke();
		ctx.beginPath();
		ctx.moveTo(x0, 0);
		ctx.lineTo(x0, size_y);
		ctx.setLineDash([1, 2]);
		ctx.strokeStyle = "black";
		ctx.stroke();
		ctx.setLineDash([]);
		fn = fn.replace(/\*\*2/g, "\u00B2").replace(/\*\*3/g, "\u00B3").replace(/\*/g, "");
		ctx.fillText(fn, size_x * (x1 + (x2 - x1) / 2) / 10, max_y);
	}
	ctx.restore();
}
</script>
</head>

<body onload='DrawFn()'>
Y2(x)=<input type=text name=Fn_X value='0..2:x+1' onchange='DrawFn()' style=color:magenta><br>
Y3(x)=<input type=text name=Fn_X value='2..5:x**2+4' onchange='DrawFn()' style=color:red><br>
Y4(x)=<input type=text name=Fn_X value='5..10:x**2-2*x+1' onchange='DrawFn()' style=color:orange><br>
<canvas width=400px height=200px></canvas>
</body>

IZUM 24.08.2022 07:59

Вложений: 2
Замечено, что на балке, имеющей чередующиеся между собой неподвижные опоры и подвижные шарниры, при наличии участка, где если сделать подъём точки в бесшарнирном пролете, то можно получить вертикальное перемещение шарниров, как показано на картинке. Как с помощью JS описать этот процесс с выдачей необходимых результатов?

IZUM 24.08.2022 08:02

Вложений: 1
Вот так лучше рассмотреть рисунки

IZUM 24.08.2022 11:09

Вложений: 1
Rise,
Я попробовал к построенным Вами графикам пристроить оси координат, но получилось так, что все графики оказались сдвинутыми влево, в отрицательную область Х. Помогите, пожалуйста исправить этот сдвиг.
[HTML]<!DOCTYPE html> 
<html> 
<head> 
 <meta charset="utf-8"> 
 <link href="./GrafPL.css" rel="stylesheet">
</head> 
<body style= "margin": 0;> 
 <canvas id="canvas_plot" width="700" height="500"></canvas>
 <script src="./GrafPL.js"></script>
</body> 

</html>[/HTML]const canvasPlot = document.getElementById(`canvas_plot`);
const ctx = canvasPlot.getContext(`2d`);

//Рисуем сетку
const canvasPlotWidth = canvasPlot.clientWidth;
const canvasPlotHeight = canvasPlot.clientHeight;

const scaleX = 30; //расстояние между элементами сетки по Х
const scaleY = 30; //расстояние между элементами сетки по Y

//Рисуем главные оси
       // Обычное расположение осей     
//const xAxis = canvasPlotWidth / 2; 
//const yAxis = canvasPlotHeight / 2;
       // Уточнённое расположение осей за счёт сдвига осей
const xAxis = Math.round(canvasPlotWidth / scaleX / 2) * scaleX; 
const yAxis = Math.round(canvasPlotHeight / scaleY / 2) * scaleY;

// Выбор шрифта и его вид, размер

ctx.font = `${Math.round(scaleX / 2)}px Arial`;
ctx.textAlign = `left`;
ctx.textBaseline = `top`;

ctx.beginPath();
ctx.strokeStyle = `#f5f0e8`;


var zz = 5; //Зазор между осями и числами нумерации чисел

for (let i = 0; i <= canvasPlotWidth; i = i + scaleX) {
	ctx.moveTo(i, 0);
	ctx.lineTo(i, canvasPlotHeight); // нанесение вертикальных линий разметки
	
	ctx.fillText((i - xAxis) / scaleX, i + zz, yAxis + zz); //нанесение числовых значений на ось Х
}

for (let i = 0; i <= canvasPlotHeight; i = i + scaleY) {
	ctx.moveTo(0, i);
	ctx.lineTo(canvasPlotWidth, i); // нанесение горизонтальных линий разметки
	
	ctx.fillText((yAxis - i) / scaleY, xAxis + zz, i + zz); //нанесение числовых значений на ось Y
}
ctx.stroke();
ctx.closePath(); //закрыть путь, чтобы рисовать новые линии другим цветом


var zxy = 20; //Зазор между осями и обозначением этих осей  x,y 
ctx.beginPath();
ctx.strokeStyle = `#000000`;
	ctx.moveTo(xAxis, 0);
	ctx.lineTo(xAxis, canvasPlotHeight);
	ctx.fillText(`y`, xAxis - zxy, 0 ); // означение оси Y
	ctx.moveTo(0, yAxis);
	ctx.lineTo(canvasPlotWidth, yAxis);
	ctx.fillText(`x`, canvasPlotWidth - zxy, yAxis - zxy ); // означение оси X
ctx.stroke();
ctx.closePath(); //закрыть путь, чтобы рисовать новые линии другим цветом

//ПРЕОБРАЗОВАНИЕ CANVAS СИСТЕМУ КООРДИНАТ В ДЕКАРТОВУ СИСТЕМУ КООРДИНАТ
// Рисуем график функции
//ctx.fillStyle = `#ff0000`; // цвет графика чёрный
//ctx.lineWidth = 0.5  //толщина линии
//for (let i = 0; i <= canvasPlotWidth; i++) {
	//const x = (i - xAxis) / scaleX;
	//const y = Math.pow(x, 2); //степенная фукция, y=x^2
	//ctx.fillRect(x * scaleX + xAxis, yAxis - scaleY * y, 4, 4);
//}

function createPath(x1, x2, y, sx, sy) {
  const path = new Path2D();
  
  for (let x = x1; x <= x2; x++)
	  {
	
    if (x == x1) {
      path.moveTo(x * sx, y(x) * sy);
    } else {
      path.lineTo(x * sx, y(x) * sy);
    }
  }
  return path;
}
 
 ctx.translate(0, 200);
ctx.scale(1, -0.5);
 
ctx.strokeStyle = 'red';
ctx.lineWidth = 4  //толщина линии
ctx.stroke(createPath(0,  2, x => x + 1, 30, 1));
ctx.strokeStyle = 'green';
ctx.stroke(createPath(2,  5, x => x ** 2 + 4, 30, 1));
ctx.strokeStyle = 'blue';
ctx.stroke(createPath(5, 10, x => x ** 2 + 2 * x + 1, 30, 1));

Alikberov 24.08.2022 21:21

A вот такой вариант не подойдёт?
<html><head>
<script>
class Functions {
	constructor(props) {
		this.grid = {
			min_x	:props && "min_x" in props ? props.min_x : -50,
			max_x	:props && "max_x" in props ? props.max_x : 100,
			min_y	:props && "min_y" in props ? props.min_y : -80,
			max_y	:props && "max_y" in props ? props.max_y : 100,
			step_x	:(props && props.step_x) || 10,
			step_y	:(props && props.step_y) || 20,
			size	:(props && props.size) || 25
		};
		this.domElement = document.createElement("canvas");
		this.domElement.width = props && props.width || 640;
		this.domElement.height = props && props.height || 480;
		this.context = this.domElement.getContext("2d");
		this.clear();
		if(props && props.fn)
			this.draw(props.fn);
		return this;
	}
	clear() {
		var	from_x = 20, last_x = this.domElement.width - 8;
		var	from_y = 8, last_y = this.domElement.height - 24;
		var	width = last_x - from_x;
		var	height = last_y - from_y;
		var	size_x = this.grid.max_x - this.grid.min_x;
		var	size_y = this.grid.max_y - this.grid.min_y;
		var	left, right, top, bottom;
		var	i, x, y;
		this.context.fillStyle = "white";
		this.context.fillRect(0, 0, this.domElement.width, this.domElement.height);
		this.context.strokeStyle = "black";
		this.context.fillStyle = "black";
		this.context.textAlign = "right";
		this.context.beginPath();
		if(this.grid.min_x * this.grid.max_x >= 0 && this.grid.min_y * this.grid.max_y >= 0) {
			this.context.rect(from_x, from_y, width, height);
			left = from_x + 2;
			right = last_x - 2;
			top = from_y + 2;
			bottom = last_y - 2;
		} else {
			left = from_x - width / size_x * this.grid.min_x;
			right = left;
			top = last_y + height / size_y * this.grid.min_y;
			bottom = top;
			this.context.moveTo(left, from_y);
			this.context.lineTo(left, last_y);
			this.context.moveTo(from_x, top);
			this.context.lineTo(last_x, top);
		}
		for(i = this.grid.min_x; i <= this.grid.max_x; i += this.grid.step_x) {
			x = from_x + (i - this.grid.min_x) / size_x * width;
			this.context.moveTo(x, top - 2);
			this.context.lineTo(x, top + 2);
			this.context.moveTo(x, bottom - 2);
			this.context.lineTo(x, bottom + 2);
			this.context.fillText(String(i), x + 4, bottom + 14);
		}
		for(i = this.grid.min_y; i <= this.grid.max_y; i += this.grid.step_y) {
			y = from_y + (i - this.grid.min_y) / size_y * height;
			this.context.moveTo(left - 2, y);
			this.context.lineTo(left + 2, y);
			this.context.moveTo(right - 2, y);
			this.context.lineTo(right + 2, y);
			this.context.fillText(String((this.grid.max_y - i + this.grid.min_y)), left - 4, y + 8);
		}
		this.context.stroke();
		this.context.fillText("X", right, bottom);
		this.context.textAlign = "left";
		this.context.fillText("Y", left, top + 12);
		return this;
	}
	draw(functions) {
		var	from_x = 20, last_x = this.domElement.width - 8;
		var	from_y = 8, last_y = this.domElement.height - 24;
		var	width = last_x - from_x;
		var	height = last_y - from_y;
		var	size_x = this.grid.max_x - this.grid.min_x;
		var	size_y = this.grid.max_y - this.grid.min_y;
		for(var grafic of functions) {
			var	min_x = Math.max(this.grid.min_x, grafic.x1 || 0);
			var	max_x = Math.min(this.grid.max_x, grafic.x2 || 0);
			var	min_y = Math.max(this.grid.min_y, grafic.y1 || 0);
			var	max_y = Math.min(this.grid.max_y, grafic.y2 || 0);
			var	fn = grafic.fn;
			var	left = from_x - this.grid.min_x / size_x * width + width / size_x * min_x;
			var	right = last_x - this.grid.max_x / size_x * width + width / size_x * max_x;
			var	top = last_y + this.grid.min_y / size_y * height + height / size_y * min_y;
			var	bottom = last_y + this.grid.min_y / size_y * height + height / size_y * max_y;
			var	maxi, exprs;
			this.context.textAlign = "right";
			if(fn) {
				exprs = fn.replace(/\*\*2/g, "\u00B2").replace(/\*\*3/g, "\u00B3").replace(/\*/g, "");
				fn = fn.replace(/(cos|sin|tan|exp)/g, "Math.$1");
				if(isFinite(grafic.x1) && isFinite(grafic.x2)) {
					this.context.beginPath();
					this.context.strokeStyle = grafic.color || "black";
					maxi = this.grid.min_y;
					for(var x0 = left; x0 < right; ++ x0) {
						var	x = (x0 - from_x) / width * size_x + this.grid.min_x, y = 0;
						try {
							y = eval(fn);
							y = y / size_y * height;
							maxi = Math.max(maxi, y);
						} catch(err) {
						}
						this.context.lineTo(x0, bottom - y);
					}
					this.context.stroke();
					this.context.beginPath();
					this.context.moveTo(x0, from_y);
					this.context.lineTo(x0, last_y);
					this.context.setLineDash([1, 2]);
					this.context.strokeStyle = "black";
					this.context.stroke();
					this.context.setLineDash([]);
					this.context.fillText(exprs, left + (right - left) / 2, bottom - maxi / 2);
				} else
				if(isFinite(grafic.y1) && isFinite(grafic.y2)) {
					this.context.beginPath();
					this.context.strokeStyle = grafic.color || "black";
					maxi = this.grid.min_x;
					for(var y0 = top; y0 < bottom; ++ y0) {
						var	x = 0, y = (bottom - y0 - from_y) / height * size_y + this.grid.min_y;
						try {
							x = eval(fn);
							x = x / size_x * width;
							maxi = Math.max(maxi, x);
						} catch(err) {
						}
						this.context.lineTo(left - x, last_y - y0 - (this.grid.min_y) / size_y * height);
					}
					this.context.stroke();
					this.context.beginPath();
					this.context.moveTo(from_x, last_y - y0 - (this.grid.min_y) / size_y * height);
					this.context.lineTo(last_x, last_y - y0 - (this.grid.min_y) / size_y * height);
					this.context.setLineDash([1, 2]);
					this.context.strokeStyle = "black";
					this.context.stroke();
					this.context.setLineDash([]);
					this.context.fillText(exprs, left + maxi / 2, top + (bottom - top) / 2);
				}
			}
		}
	}
}

function Draw_Fn() {
	document.body.appendChild(
		new Functions({
			min_x: 0,
			max_x: 10,
			min_y: 0,
			max_y: 100,
			step_x: 1,
			step_y: 20,
			width: 390,
			height: 120,
			fn: [
				{
					x1:0, x2:100,
					color: "red",
					fn: "x**2"
				}
			]
		}).domElement
	);
	document.body.appendChild(document.createElement("hr"));
	document.body.appendChild(
		new Functions({
			min_x: 0,
			max_x: 10,
			min_y: 0,
			max_y: 100,
			step_x: 1,
			step_y: 20,
			width: 390,
			height: 120,
			fn: [
				{
					x1:-1, x2:2,
					color: "magenta",
					fn: "x+1"
				},
				{
					x1:2, x2:5,
					color: "red",
					fn: "x**2+4"
				},
				{
					x1:5, x2:10,
					color: "orange",
					fn: "x**2-2*x+1"
				}
			]
		}).domElement
	);
	document.body.appendChild(document.createElement("hr"));
	document.body.appendChild(
		new Functions({
			min_x: -5,
			max_x: 10,
			min_y: -50,
			max_y: 100,
			step_x: 5,
			step_y: 25,
			width: 390,
			height: 240,
			fn: [
				{
					x1:0, x2:10,
					color: "red",
					fn: "x**2"
				},
				{
					y1:-35, y2:50,
					color: "blue",
					fn: "sin(y/10)"
				},
				{
					x1:-5, x2:10,
					color: "green",
					fn: "exp(x/2)"
				}
			]
		}).domElement
	);
}
</script>
<style>
canvas {
	display: block;
}
body {
	background-color: silver;
}
</style>
</head>

<body onload='Draw_Fn()'>
</body>

рони 24.08.2022 21:24

Цитата:

Сообщение от Alikberov
A вот такой вариант не подойдёт?

офигеть)))

IZUM 24.08.2022 22:29

Rise, оказалось так всё просто.. Спасибо !

IZUM 24.08.2022 22:36

Alikberov,
Графики, получились красивые, особенно, с осями координат. Спасибо за ответ. Однако, графики не имеют своих интервалов существования на оси Х, потому что они существуют на всей числовой оси Х и сходятся в одной точке (0, 0)

IZUM 24.08.2022 22:54

Alikberov, Только, что опробовал Ваш код, и оказалось, что Вы построили все возможные варианты построения графиков. Поэтому, очень прошу извинить меня за мою невнимательность и ещё раз большое спасибо. Ваш код не просто правильный, но совершенно полноценный.

IZUM 24.08.2022 23:03

Alikberov,
Было бы здорово, если бы Вы, общий код разделили на три кода: Соответственно, для одного графика, для графиков на разных интервалах, и графиков проходящих через (0,0). Сам, я боюсь чт что-нибудь не так сделаю или допушу досадную ошибку. Заранее благодарен.

Alikberov 25.08.2022 01:39

Увы! Код написан довольно быстро и местами небрежно. Имеет ряд недоработок:
  • Отсутствует сетка, хотя параметр (строка 12) для указания размера ячеек имеется
  • Отсутствует клиппинг рабочей области и график может выйти за её пределы
  • Строка математического выражения к графику выводится чуть ли ни хаотически: Нужно добавить параметр для ручного позиционирования текста
  • Стиль текста никак не настраивается
  • Нужно добавить опцию подавления пунктирного разделителя
  • Самое неприятное: Построение графиков по оси Y (строки 127-134) имеет много грубых досадных ошибок (привет двойкам по математике) и я не успел отладить (методом проб и ошибок) эту часть кода
Цитата:

Было бы здорово, если бы Вы, общий код разделили на три кода
А там специально я код вызова функции разделил на три части (строки 152-170, 172-200 и 202-230), чтобы можно было скопировать и изменять по своему усмотрению.
Вот только комментарии не успел раскидать, из-за чего код слишком мутно выглядит.

Вот фрагмент с пояснениями.
var	meine_diagramme = {
	min_x: 0,		// Минимум по оси X на графике
	max_x: 10,		// Максимум по оси X на графике
	min_y: 0,		// Минимум по оси Y на графике
	max_y: 100,		// Максимум по оси Y на графике
	step_x: 1,		// Шаг цифровой разметки по оси X под графиком
	step_y: 20,		// Наш цифровой разметки по оси Y слева от графика
	width: 390,		// Ширина Canvas-области - масштаб графика 
	height: 120,	// Высота Canvas-области
	fn: [			// Здесь можем перечислить массивом все наши функции
		{
			x1: 0,			// Начальная точка на оси X
			x2: 100,		// Конечная точка на оси X
			color: "red",	// Цвет линии построения графика
			fn: "x**2"		// Ведущая функция на графике
		}
	]
};

// Создаём объект графика и передаём ему реквизиты
var grafik = new Functions(meine_diagramme);

// Добавляем его на страницу
document.body.appendChild(grafik.domElement);

// Теперь добавим вторую функцию
var mein_diagramm = {
		x1: 2.5,			// Начальная точка на оси X
		x2: 7.5,			// Конечная точка на оси X
		color: "blue",	// Цвет линии построения графика
		fn: "30*sin(10*x)+60"
	};

// Пауза перед добавлением второго графика
setTimeout(`grafik.draw([mein_diagramm])`, 5000);

// Пауза перед очищением области графика
setTimeout(`grafik.clear()`, 10000);

// Пауза перед отображением только второго графика`);
setTimeout(`grafik.draw([mein_diagramm])`, 15000);

// Пауза перед смещением оси путём коррекции минимума на сетке
setTimeout(`grafik.grid.min_x = -12; grafik.clear()`, 20000);

// Пауза перед отображением двух графиков
setTimeout(`mein_diagramm.x1 = -10; grafik.draw([mein_diagramm, meine_diagramme.fn[0]])`, 25000);

IZUM 25.08.2022 11:33

Alikberov,
Уважаемый Alikberov, так как, я не такой опытный, как Вы, мне так и не удалось Ваш код разделить на три кода ( три типа построения графиков)
Убрал строки 152-170 с целью убрать первый тип графиков и, как и ожидалось ничего не получилось (где то ошибка). Вернее, после этого пошли ошибки за ошибкой, которые надо было исправлять. Убедительно прошу разделить общий файл на три по типам построения графиков. Если, есть возможность дайте хоть, какие нибудь главные комментарии.

IZUM 25.08.2022 18:14

Alikberov, Извините, опять поторопился со своим последним сообщением. Мне удалось Ваш код разделить на три кода ( три типа построения графиков). Правда, потратил на это несколько часов.

Alikberov 26.08.2022 04:29

Вот Вам Конструктор Графиков с "горячей отладкой".
(Можно изменять любые параметры как мышкой, так и JSON.)

Правда, там не всё работает так, как хотелось бы мне. Но это поправимо в дальнейшем.

P.S.: Класс переименовал, добавил сетку и легенду.

Rise 26.08.2022 16:26

Alikberov,
График не реагирует на изменения шкалы, а должен по идее.

Могу подсказать, где подсмотреть можно на эту тему, если интересно.

IZUM 26.08.2022 16:30

Вложений: 1
Alikberov, Я взял за основу Ваш код для построения графиков, но не смог нарисовать обыкновенные геометрические фигуры ромба и окружности, которые привязаны к определенной координате (См.картинку). Попробовал нарисовать обыкновенный отрезок, а он уехал, вобще, куда-то из области координат. Ваш код настолько насышен различными тегами, что я точно не смогу разобраться в чём дело. Видимо здесь более глубинные причины разрыва осей координат от геометрических фигур. Прошу, если возможно, упростить или сделать понятным высланный мной код HTML, который я выделил из общего Вашего кода. И, самое главное - нарисовать ромб и окружность, показанные на картинке. Их размеры должны быть сооизмеримы с координатными осями.
<!DOCTYPE html> 
<html><head>
<canvas id="canvas"></canvas>

<script>

var
canv = document.getElementById(`canvas`),
ctx = canv.getContext(`2d`); // рисуем в 2d

class Functions {
	constructor(props) { // описание и атрибуты
		this.grid = { // это сетка
			min_x	:props && "min_x" in props ? props.min_x : -50,
			max_x	:props && "max_x" in props ? props.max_x : 100,
			min_y	:props && "min_y" in props ? props.min_y : -80,
			max_y	:props && "max_y" in props ? props.max_y : 100,
			step_x	:(props && props.step_x) || 10,
			step_y	:(props && props.step_y) || 20,
			size	:(props && props.size) || 25
		};
		this.domElement = document.createElement("canvas");
		this.domElement.width = props && props.width || 640;
		this.domElement.height = props && props.height || 480;
		this.context = this.domElement.getContext("2d");
		this.clear();
		if(props && props.fn)
			this.draw(props.fn);
		return this;
	}
	clear() {
		var	from_x = 20, last_x = this.domElement.width - 8;
		var	from_y = 8, last_y = this.domElement.height - 24;
		var	width = last_x - from_x;
		var	height = last_y - from_y;
		var	size_x = this.grid.max_x - this.grid.min_x;
		var	size_y = this.grid.max_y - this.grid.min_y;
		var	left, right, top, bottom;
		var	i, x, y;
		this.context.fillStyle = "white";
		this.context.fillRect(0, 0, this.domElement.width, this.domElement.height);
		this.context.strokeStyle = "black";
		this.context.fillStyle = "black";
		this.context.textAlign = "right";
		this.context.beginPath();
		if(this.grid.min_x * this.grid.max_x >= 0 && this.grid.min_y * this.grid.max_y >= 0) {
			this.context.rect(from_x, from_y, width, height);
			left = from_x + 2;
			right = last_x - 2;
			top = from_y + 2;
			bottom = last_y - 2;
		} else {
			left = from_x - width / size_x * this.grid.min_x;
			right = left;
			top = last_y + height / size_y * this.grid.min_y;
			bottom = top;
			this.context.moveTo(left, from_y);
			this.context.lineTo(left, last_y);
			this.context.moveTo(from_x, top);
			this.context.lineTo(last_x, top);
		}
		for(i = this.grid.min_x; i <= this.grid.max_x; i += this.grid.step_x) {
			x = from_x + (i - this.grid.min_x) / size_x * width;
			this.context.moveTo(x, top - 2);
			this.context.lineTo(x, top + 2);
			this.context.moveTo(x, bottom - 2);
			this.context.lineTo(x, bottom + 2);
			this.context.fillText(String(i), x + 4, bottom + 14);
		}
		for(i = this.grid.min_y; i <= this.grid.max_y; i += this.grid.step_y) {
			y = from_y + (i - this.grid.min_y) / size_y * height;
			this.context.moveTo(left - 2, y);
			this.context.lineTo(left + 2, y);
			this.context.moveTo(right - 2, y);
			this.context.lineTo(right + 2, y);
			this.context.fillText(String((this.grid.max_y - i + this.grid.min_y)), left - 4, y + 8);
		}
		this.context.stroke();
		this.context.fillText("X", right, bottom);
		this.context.textAlign = "left";
		this.context.fillText("Y", left, top + 12);
		return this;
	}
	draw(functions) {
		var	from_x = 20, last_x = this.domElement.width - 8;
		var	from_y = 8, last_y = this.domElement.height - 24;
		var	width = last_x - from_x;
		var	height = last_y - from_y;
		var	size_x = this.grid.max_x - this.grid.min_x;
		var	size_y = this.grid.max_y - this.grid.min_y;
		for(var grafic of functions) {
			var	min_x = Math.max(this.grid.min_x, grafic.x1 || 0);
			var	max_x = Math.min(this.grid.max_x, grafic.x2 || 0);
			var	min_y = Math.max(this.grid.min_y, grafic.y1 || 0);
			var	max_y = Math.min(this.grid.max_y, grafic.y2 || 0);
			var	fn = grafic.fn;
			var	left = from_x - this.grid.min_x / size_x * width + width / size_x * min_x;
			var	right = last_x - this.grid.max_x / size_x * width + width / size_x * max_x;
			var	top = last_y + this.grid.min_y / size_y * height + height / size_y * min_y;
			var	bottom = last_y + this.grid.min_y / size_y * height + height / size_y * max_y;
			var	maxi, exprs;
			this.context.textAlign = "right";
			if(fn) {
				exprs = fn.replace(/\*\*2/g, "\u00B2").replace(/\*\*3/g, "\u00B3").replace(/\*/g, "");
				fn = fn.replace(/(cos|sin|tan|exp)/g, "Math.$1");
				if(isFinite(grafic.x1) && isFinite(grafic.x2)) {
					this.context.beginPath();
					this.context.strokeStyle = grafic.color || "black";
					maxi = this.grid.min_y;
					for(var x0 = left; x0 < right; ++ x0) {
						var	x = (x0 - from_x) / width * size_x + this.grid.min_x, y = 0;
						try {
							y = eval(fn);
							y = y / size_y * height;
							maxi = Math.max(maxi, y);
						} catch(err) {
						}
						this.context.lineTo(x0, bottom - y);
					}
					this.context.stroke();
					this.context.beginPath();
					this.context.moveTo(x0, from_y);
					this.context.lineTo(x0, last_y);
					this.context.setLineDash([1, 2]);
					this.context.strokeStyle = "black";
					this.context.stroke();
					this.context.setLineDash([]);
					this.context.fillText(exprs, left + (right - left) / 2, bottom - maxi / 2);
				} else
				if(isFinite(grafic.y1) && isFinite(grafic.y2)) {
					this.context.beginPath();
					this.context.strokeStyle = grafic.color || "black";
					maxi = this.grid.min_x;
					for(var y0 = top; y0 < bottom; ++ y0) {
						var	x = 0, y = (bottom - y0 - from_y) / height * size_y + this.grid.min_y;
						try {
							x = eval(fn);
							x = x / size_x * width;
							maxi = Math.max(maxi, x);
						} catch(err) {
						}
						this.context.lineTo(left - x, last_y - y0 - (this.grid.min_y) / size_y * height);
					}
					this.context.stroke();
					this.context.beginPath();
					this.context.moveTo(from_x, last_y - y0 - (this.grid.min_y) / size_y * height);
					this.context.lineTo(last_x, last_y - y0 - (this.grid.min_y) / size_y * height);
					this.context.setLineDash([1, 2]);
					this.context.strokeStyle = "black";
					this.context.stroke();
					this.context.setLineDash([]);
					this.context.fillText(exprs, left + maxi / 2, top + (bottom - top) / 2);
				}
			}
		}
	}
}

function Draw_Fn() {
	document.body.appendChild(
		new Functions({
			min_x: 0,
			max_x: 40,
			min_y: -8,
			max_y: 8,
			step_x: 1,
			step_y: 20,
			width: 600,
			height: 200,
			fn: [
				{
					x1:0, x2:6,
					color: "red",
					fn: "x**2"
				}
			]
		}).domElement
	);
	
}


//линия 1

ctx.beginPath();
ctx.moveTo(200, 0);
ctx.lineTo(300, 1000);
ctx.stroke();
ctx.closePath();
</script>
<style>
canvas {
	display: block;
}
body {
	background-color: silver;
}
</style>
</head>

<body onload='Draw_Fn()'>
</body>

Alikberov 26.08.2022 17:25

Цитата:

Сообщение от Rise (Сообщение 547621)
График не реагирует на изменения шкалы, а должен по идее.

Этo очень плохо!:blink:
У меня в двух браузерах (Chrome и Firefox ESR) отрабатывается нормально.
Цитата:

Сообщение от Rise (Сообщение 547621)
Могу подсказать, где подсмотреть можно на эту тему, если интересно.

Конечно!
Очень буду рад помощи в этой бесконечной и бессмыленной битве с кросс-браузерностью.:thanks:

Цитата:

Сообщение от IZUM (Сообщение 547622)
Я взял за основу Ваш код для построения графиков, но не смог нарисовать обыкновенные геометрические фигуры ромба и окружности, которые привязаны к определенной координате (См.картинку).

И не мудрено, так как в основе этого класса ставилась задача простого вывода графика.
Класс просто не расчитывался для внешего вмешательства.
Цитата:

Сообщение от IZUM (Сообщение 547622)
Попробовал нарисовать обыкновенный отрезок, а он уехал, вобще, куда-то из области координат.

Чем подкидываете мне идею преобразования координат. Только это займёт время.
Цитата:

Сообщение от IZUM (Сообщение 547622)
Прошу, если возможно, упростить или сделать понятным высланный мной код HTML, который я выделил из общего Вашего кода. И, самое главное - нарисовать ромб и окружность, показанные на картинке. Их размеры должны быть сооизмеримы с координатными осями.

Я просто добавлю Вам метод для преобразования массива Ваших координат в координаты шкалы.

Alikberov 26.08.2022 20:22

Вложений: 1
Цитата:

Сообщение от IZUM (Сообщение 547622)
И, самое главное - нарисовать ромб и окружность, показанные на картинке. Их размеры должны быть сооизмеримы с координатными осями.

(Запустить на gist)
строки #503
// Рисуем на первом графике
	hChart.context.strokeStyle = "blue";
	hChart.transform();	// Трансформации контекста холста под формат графика
	hChart.context.beginPath();
	// Рисуем ромб в точке 7,0
	hChart.context.moveTo(7, 1);
	hChart.context.lineTo(7.5, 0);
	hChart.context.lineTo(7, -1);
	hChart.context.lineTo(6.5, 0);
	hChart.context.closePath();
	// Рисуем эллипс в точке 11,0
	hChart.context.moveTo(11, 0);
	hChart.context.ellipse(11, 0, 0.5, 0.5, 0, 0, 2 * Math.PI);
	// Сбрасываем трансформацию, чтобы толшина кисти восстановилась
	hChart.context.setTransform(1, 0, 0, 1, 0, 0);
	// Отображаем ромб и эллипс
	hChart.context.stroke();
Тaк подойдёт (смотрите изображение)?

IZUM 26.08.2022 20:38

Изображения то, что нужно!

IZUM 27.08.2022 09:46

Вложений: 2
1. Если, построить ломанную линию (см.рис.1) способом построения геометрической фигуры, то , видимо, нельзя будет определить значения хc и yc в любой точке ломанной линии и площади фигуры под ломанной линией на любом отрезке xn…xk (см. рис.2). Поэтому, наверное, надо данную ломанную линию построить в виде графика функции. Координаты по X : 0, 5, 10, 16, 21, 30, 33. По Y: 0, 2.24, -1.49, 1.5, -1, 0.5, 0.
2. В точках X: 8, 13, 19, 27, 33. Надо построить ромбы, в точках Х: 5, 10, 16, 21, 30. надо построить окружности (не эллипсы). Методом копирования. Приведённые выше числовые данные могут быть произвольными значениями. Хорошо бы было, если были бы удалены теги (строки) относящиеся к остальным графикам и оставлено в коде только то, что относится к графику ломанной линии, ромбов и окружностей. Прошу помочь, так как, мне это точно не осилить….

IZUM 27.08.2022 09:58

Вложений: 2
высыланные в предыдущем сообщении картинки не совсем разборчивы. Высылаю ещё раз

Alikberov 27.08.2022 11:58

Цитата:

Сообщение от IZUM (Сообщение 547632)
Прошу помочь, так как, мне это точно не осилить….

Зaпустите gist: Там я добавил текстовое поле с примером, как чертить ромбы и эллипсы прямо на графике.
Следуйте правилам контекста Canvas и добавляйте свои инструкции.
Кликайте на график, чтобы изменения вступили в силу.

Если допустите ошибку, текстовое поле поменяет цвет.

Alikberov 29.08.2022 00:37

Вложений: 1
Цитата:

Сообщение от IZUM (Сообщение 547632)
1. Если, построить ломанную линию (см.рис.1) способом построения геометрической фигуры, то , видимо, нельзя будет определить значения хc и yc в любой точке ломанной линии и площади фигуры под ломанной линией на любом отрезке xn…xk (см. рис.2)

Этo элементарная математическая задача и сейчас как программист (в математике я не очень) я попытаюсь её решить.

Дан треугольник с длиной основания 8 пунктов.
Вершина треугольника находится в пункте 5 на высоте 2.24 пункта.

Опишем его переменными:
Ax = 0, Bx = 8
Cx = 5, Cy = 2.24


По сути, мы имеем тут два прямоугольных треугольника:
Ax = 0, Cx = 5, Cy = 2.24	// левый треугольник
Cx = 5, Bx = 8, Cy = 2.24	// правый прямоугольник


Так как из двух одинаковых прямоугольников можем получить прямоугольник, находим их площади через площади прямоугольников с делением пополам.

Находим площадь:
S_left_rect = (Cx - Ax) * Cy	// площадь левого прямоугольника
S_right_rect = (Bx - Cx) * Cy
S_left_triangle = S_left_rect / 2	// площадь левого треугольника AxCxCy
S_right_triangle = S_right_rect / 2	// площадь правого треугольника CxBxCy
S_triangle = S_left_triangle + S_right_triangle	// площадь целого треугольника AxBxCy


Теперь нужно узнать площадь заштрихованной области xn,yn,xk,yk.
Опишем переменными:
xn = 3	// начало штриховки
xk = 6	// конец штриховки
yn = ??? // нужно найти
yk = ??? // нужно найти


Используя метод вычислительного построения линии, находим yn:
delta_x = Cx - Ax	// длина отрезка AxCx (5 пунктов)
yn = (xn - Ax) / delta_x * Cy	// (3 - 0) / 5 * 2.24 = 1.344


Используя метод вычислительного построения линии, находим yk:
delta_x = Bx - Cx	// длина отрезка CxBx (3 пункта)
yk = (Bx - xk) / delta_x * Cy	// (8 - 6) / 3 * 2.24 = 1.493


Теперь мы имеем:
xn = 3	// начало штриховки
xk = 6	// конец штриховки
yn = 1.344 // минимальная высота начала штриховки
yk = 1.493 // минимальная высота конца штриховки


Для начала нужно разобраться, что мы видим под этой штриховкой вообще:
Прямоугольник №1: xn,Cx,yn
Прямоугольник №2: Cx,xk,yk
Прямоугольный треугольник №1: xn,yn,Cy
Прямоугольный треугольник №2: Cx,xk,Cy

Площадь будем искать подобным методом: Через вычитание суммы площадей двух нештрихованных треугольников над заштрихованной областью.

Находим площадь незаштрихованных треугольников и вычитаем её из общей площади:
S_left_rect = (Cx - xn) * Cy	// Прямоугольник xnCxCy: (5 - 3) * 2.24 = 4.48
S_right_rect = (xk - Cx) * Cy	// Прямоугольник xkCxCy: (6 - 5) * 2.24 = 2.24

S_left_triangle = (Cx - xn) * (Cy - yn) / 2	// Треугольник ynCxCy: (5 - 3) * (2.24 - 1.344) / 2 = 0.896
S_right_triangle = (xk - Cx) * (Cy - yk) / 2	// Треугольник xkCxCy: (6 - 5) * (2.24 - 1.493) / 2 = 0.3735


Теперь складываем площади двух нижних заштрихованных прямоугольников с площадами треугольников над ними:
S_hatching = S_left_rect + S_left_triangle + S_right_rect + S_right_triangle	// 7.9895

Как видно, всё не так уж сложно и требует неких навыков программисткого мышления.
Цитата:

Сообщение от IZUM (Сообщение 547632)
2. В точках X: 8, 13, 19, 27, 33. Надо построить ромбы, в точках Х: 5, 10, 16, 21, 30. надо построить окружности (не эллипсы). Методом копирования. Приведённые выше числовые данные могут быть произвольными значениями. Хорошо бы было, если были бы удалены теги (строки) относящиеся к остальным графикам и оставлено в коде только то, что относится к графику ломанной линии, ромбов и окружностей. Прошу помочь, так как, мне это точно не осилить….

А здесь Вы идёте неверным путём!
Так как нужно всего навсего строить ломанную по координатам из массива, на оси X отмечать начало очередной линии, а вот ромбики нужно чертить в точке прохождения линий через нулевую отметку на Y.

Запустите gist: Я добавил отрисовку ломанной по массиву с ромбиками и кружочками.

Вoт здесь я разместил простой скрипт со всеми нужными формулами расчёта площади фигуры под ломанной линией на любом отрезке (в пределах одного треугольника).
(Расчёт площади по ломанной на отрезке со смежными треугольниками требует простого сложения таких площадей с нехитрой эвристикой…)


Часовой пояс GMT +3, время: 00:28.