Pull to refresh

Comments 54

Не могу разглядеть — где гарантия, что производная f(x) на таком графика меньше бесконечности (и f(x) однозначна)? То есть, что кривая по ходу слева направо в каком либо месте не загнётся справа налево.

Не смог увидеть обоснование зачем эстетическое закругление Безье, которое не проходит через заданные точки, использовать вместо строго интерполяционных полиномов, предназначенных для этого.
Дайте мне точки, при которых кривая загнется, и я удалю статью.

Читайте пункт «К приятному!».
«Ты не романтик!» /Робот Вертер/
Корректность метода всегда на совести предлагающего метод.
На вики можно найти анимацию с построением кривой Безье. Если опорные точки в ней будут расположены по возрастанию x-координаты, то все промежуточные точки, в т.ч. и последняя, рисующая сам график, будут всегда двигаться слева направо.

При данном выборе опорных точек разница в x-координате между B1 и A не больше половины таковой между B и A (поскольку расстояние между A и B1 равно половине, а длина его проекции на ось x, значит, не больше). Для опорной точки, лежащей справа от B, (назовём её A1) выполнено то же самое. Значит, B, A1, B1, A лежат по возрастанию x, а это нам и требуется.

Есть, правда, один плохой случай, когда A1 и B1 лежат на одной вертикальной прямой, а отрезки BA1 и B1A горизонтальны, но он рассматривается отдельно: на анимации кривой Безье все точки всё равно будут двигаться вправо, кроме той, что движется по отрезку A1B1.
Не уверен, что правильно понял плохой случай, но алгоритм не должен поставить опорные точки на вертикальную прямую, если соседние слева и справа точки графика лежат на одной горизонтальной прямой с текущей точкой
Возможно, я неправильно понял алгоритм, но кажется, если синие точки ниже — это график, то две красных станут опорными.
Все правильно. Но ничего плохого не произойдет, если не брать растояние до опорных точек большим. Собственно, как Вы и говорили
UFO just landed and posted this here
https://jsfiddle.net/1oLyt2jz/4/

И более «плавнее», у автора кривая описанная а тут вписанная получается
UFO just landed and posted this here
У вас ошибка в первой строчки математики. Должно быть => cos(f)= +- sqrt(..) (забыл минус)
Корректное замечание. Но там дальше используется и +ΔX', и -ΔX', поэтому этот недочёт нивелируется
Там ΔX' идет как длинна стороны треугольника (AC1O), а длинна стороны не может быть отрицательной. Поэтому знак не учитывался. Но замечание корректное и за него спасибо
Я когда-то такую идею с равными углами встроил в свой векторный редактор… и через некоторое время осознал, что если картинка масштабируется по разному по Y и по X, то кривая вместо такого-же изменения масштаба должна поменять форму, если хочется чтобы углы между касательными остались одинаковыми. Но так как других идей так и не придумал, то всё оставил как было. Не знаю, можно ли тут что-то другое предложить.
Не уверен, но по идее, если сохранять координаты опорных точек (а это львиная доля вычислений в алгоритме), и потом масштабировать и координаты точек графика, и координаты опорных точек, и по ним заново проводить кривые Безье, то все должно работать. Нужно попробовать как-то.
Кривая поменяет форму, но выйдет ли использовать уже посчитаные координаты опорных точек… Было бы здорово.
Я тогда решил, что форма не должна меняться, так что после расчёта при заданном масштабе всё приводилось в такой вид, чтобы после изменения масштаба форма не менялась, хотя углы уже переставали быть равными. Для меня так было удобнее, тем более что я там ещё какой-то метод построения кривых Безье использовал. Давно это было, в те времена когда inkscape был мне не доступен и приходилось выкручиваться чтобы в статью картинку вставить не нарушая никаких лицензий.
Когда на третьем курсе был придуман этот алгоритм, слово «интерполяция» вселяло в меня ужас, а гугление по запросу «сглаживание графиков» не давало посильных пониманию результатов. Но как-то я дошел до кривых Безье и уж очень они мне понравились. Рисует быстро, алгоритм интуитивно понятный… Что еще надо для счастья.


Собственные алгоритмы всегда интуитивно понятнее ). Но рискну предположить, что обычный Катмулл-Ром на порядок проще и понятнее. Найти можно много где, в т.ч. и здесь https://habrahabr.ru/post/282441/
Надеюсь, что кому-то в вебе пригодится) Мне когда-то в курсаче на третьем курсе универа пригодилось))
Я долго и безрезультатно пытался сделать в коде на JSFiddle так, чтобы можно было один раз запомнить опорные точки, а потом, при масштабировании расстянуть координаты точек графика и координаты опорных точек, и заново провести кривые Безье, не пересчитывая собственно координаты опорных точек. Если у кого-то есть какие-то идеи по этому поводу (а еще лучше — реализации), то я буду очень благодарен. Или если кто-то уверен, что этого сделать не выйдет — тоже напишите. А то товарищ quverty на такую идею классную с масштабированием натолкнул…

Там есть три строчки закомментированные (№№137, 151, 152). Если их раскомментировать, то на canvas-е нарисует еще и опорные точки
Мне кажется, что уже просто то, что при масштабировании по вертикали какие-то углы перестают быть равными и надо все перестраивать, говорит что для построения графика это не особо подходит. График не должен как-то дополнительно изменяться помимо самого масштабирования. А вот на рисунках это не выглядит проблемой. А то порой приходится все эти узлы каждый раз двигать у каждого стыка когда рисуешь с использованием кривых Безье.
У меня просто чувство такое, что хоть углы и меняются, но построенные кривые Безье должны просто расстянутся. Там даже если просто в JSFiddle порастягивать график, то видно (если показать еще и опорные точки), что кривые просто расстягиваются, не меняя как-то кривизну или свои изгибы. И мне это не дает покоя. Было бы круто, если бы в веб-е получилось в риал-тайме масштабировать график)
Но руки что-то не позволяют реализовать это. То ли я туплю, то ли алгоритм таки не позволяет стягивать-расстягивать canvas без перерассчета опорных точек…
Нет, я уже на эти грабли наступал.
А было бы классно, если можно было не пересчитывать)
Но если нет — то нет. Поверю на слово
Там видимо не углы надо равными делать, а что-то другое, тогда и точки не надо пересчитывать. А на вашем конкретном графике вроде вообще не особо заметно что углы меняются и кажется что всё почти О'К
Не совсем то. Через углы я вышел на опорные точки. Посчитал их. Все, дальше за углы я забываю и иду от координат опорных точек. Координаты ОТ (опорных точек, дальше буду сокращать) я сохранил в массиве. Когда идет масштабирование, я пропорционально пытался растянуть и координаты точек графика, и координаты ОТ. И тут у меня что-то не получается… И я вот не знаю где беда — у меня в кривизне рук, или в том, что алгоритм не позволяет. По идее — все должно работать. А на практике… И я вот теряюсь в догадках. С одной стороны — там в рассчетах координат ОТ идут квадраты и корни от координат точек графика. А с другой — по ощущениях, все должно получатся при масштабировании.
А когда график начинаешь растягивать углы перестают быть равными, если они расположены не симметрично. Вроде бы так, если я что-то не упустил.
У меня просто бред получался. Я брал по умолчанию шаг по X равен 1, а по Y — то, что в исходных данных. Потом считал опорные точки и сохранял их в массив. А потом, когда отрисовывал, все масштабировал. И у меня получался бред. И я не знаю, то ли я втупил, то ли… нельзя так масштабировать. Было бы классно, если бы кто-то обосновать смог это все.
Есть идея провести один отрезок вертикально из В1 на АВ, а другой из С1 на АС и требовать равенства этих отрезков а не углов, и может тогда всё будет масштабироваться правильно.
Не то. Эти отрезки изначально не равны (если идти от равенства ∠BAB1=∠CAC1). Можно попробовать сохранять сдвиг опорных точек по X и по Y относительно точки A… И масштабировать его… Надо попробовать
Так и надо сделать их равными, тогда при масштабировании они останутся равными. А углы при вертикальном масштабировании перестают быть равными. Это правильно для графика. Я использовал равенство углов в редакторе, так как там более важно было то, что картинка правильно ведёт себя при вращении, хотя при этом получалась проблема при разном изменении масштаба по каждой оси.
Надо посоображать про это все. И попробовать в коде.
Я наверное до завтра break сделаю. А на завтра попробую что-то родить. Интересно с этим всем получается)
Просто был какой-то древний редактор (DrHalo) который со всем этим замечательно справлялся, но я что-то так и не понял как, несмотря на многочисленные эксперименты.
Понятно, конечно, что кривые проходят именно через точки, но будь по оси Y температура а по X время (рис.1), и до вершины второй кривой градусов 100, я бы запаниковал…
function drawByCurves(ctx, points) {
  ctx.beginPath();
  ctx.setLineDash([]);
  ctx.moveTo(points[0].x, points[0].y);
  var dl = 0;
  for (var i = 0; i < points.length - 2; i++) {
    var dr = (points[i+2].y - points[i].y) / 2;
    var a3 = dl + dr + 2 * (points[i].y - points[i+1].y);
    var a2 = points[i+1].y - a3 - dl - points[i].y;
    
    for (var t = 0; t <= 1; t+=.05) {
      var y = a3; y = y*t+a2; y = y*t+dl; y = y*t+points[i].y;
      ctx.lineTo(points[i].x + t * 50, y);
    }
    dl = dr;
  }
  ctx.strokeStyle = 'blue';
  ctx.stroke();
}




Без Безье
Ваш зверь пошустрее будет)

P.S.: Разве что выиграю за счет того, что у меня промежуточные точки встроенная функция отрисовывает, а у Вас — цикл с lineTo()
Ну вы же понимаете, что это зависит от скорости подкапотной реализации вашей встроенной quadraticCurveTo. И в явном цикле можно тривиально менять шаг.
Конечно.
Ваша реализация — это сплайн Катмулла-Рома?
Именно. В моей статье на хабре (ссылка выше) есть одна маленькая опечатка в формуле его расчета, в коде выше она исправлена. А вообще формулы выводятся на порядок проще, чем ваши расчеты в статье. Для любого локального сплайна. Которые, кстати, совершенно не освещены в статье на которую вы ссылаетесь (кроме Акимы), да и вообще в той статье сравнивается несравнимое — локальные сплайны с глобальными и т.п.
Так у меня же через геометрию все...)

Я про сплайны Акимы до той статьи и не слышал
Сплайн Катмулла-Рома, кстати, очень похоже отрисовывает
Потому что они оба — реализации кубического сплайна Эрмита, различие только в алгоримте расчета производных в узлах. Вообще, я наверное не умею писать статьи, если из моей это непонятно :) И как рассчитывать коэффициенты сплайна тоже.
В каком смысле оба? Кривая Безье это более общий случай чем кубический сплайн. Я прикинул коэффициенты, получилось что для того, чтобы она выродилась в обычный кубический сплайн надо опорные точки разнести по x на треть расстояния. Уже после этого подбирать коэффициенты по Эрмиту или как нибудь ещё. В примере же половина.
В смысле оба — Катмулл-Ром с Хироши Акимой. А Безье — это именно кривая, а не функция (как сплайн). Если отойти от геометрического алгоритма построения Кастельжо, то насколько я помню, она выражается через параметрические полиномы Бернштейна. Но в любом случае не могу согласиться, что она является более общим случаем сплайна.
ЗЫ если в джаваскрипте есть cubicCurve, то тривиально строится аналогичный алгоритм, как у ТС.
Кривая Безье описывается как (x(t),y(t)), где x(t) и y(t) полиномы третьего порядка. Сплайн можно описать той же формулой, если взять x = at+b. Поэтому, если у кривой Безье занулить коэффициенты при кубическом и квадратичном члене для х, то она ничем не отличается от кубического сплайна. Как я уже написал наверху, в том примере с постоянным шагом, который предложил автор поста, для этого достаточно сделать расстояние между опорными точками равным одной третьей шага. Надеюсь не переврал в расчётах.
Навскидку выглядит правдоподобно, соглашусь насчет частного случая при линейной зависимости аргумента от параметра. Честно сказать, в метод предложенный в статье не вчитывался — чукча не читатель, чукча писатель, своих вариантов вагон — девать некуда )
Зато если функции нужной не найдётся можно Безье вместо сплайнов использовать.
Это зависит от главного — о чем и был первый комментарий к статье — либо мы рисуем кривую, либо функцию.
Я имел в виду чисто в практическом смысле использования одного и того же кода для разных целей. А так да, хороший комментарий
function drawByCurves(ctx, points) {
  ctx.beginPath();
  ctx.setLineDash([]);
  ctx.moveTo(points[0].x, points[0].y);
  var dl = 0, k = 0.33;
  for (var i = 0; i < points.length - 2; i++) {
    var dr = (points[i+2].y - points[i].y) / 2 * k;
    ctx.bezierCurveTo(
    points[i].x+k*30, points[i].y+dl,    
    points[i+1].x-k*30, points[i+1].y-dr,
    points[i+1].x, points[i+1].y);
    dl = dr;
  }
  ctx.strokeStyle = 'blue';
  ctx.stroke();
}



Рулим коэффициентом k.

ЗЫ так можно и до NURBS с Кочанеками-Бартельсами дойти. Раз уж пошла такая… интерполяция )
Что-то страшное действительно. Вообще я имею в виду первая опорная точка 1/3 и вторая 2/3 — ну как тут могут получиться такие страсти?
Все правильно, по 1/3 — см. первую картинку (там и в коде k=0.33 — это именно оно). А вторая картинка при k=1.9, для демонстрации красоты ) И чтобы показать, что этот коэффициент аналогичен коэффициенту натяжения в TCB-сплайне.

Очень интересный алгоритм. Но мне надо было неравномерный наг. Я провел небольшое исследование, упростил вывод коэффициента k (тригонометрия вообще не нужна). Сразу написать коммент не мог, т.к. не было доступа. Написал сюда после опубликования этой же статьи во 2-ой версии, после принятия которой появилось разрешение писать комменты.

Sign up to leave a comment.

Articles