如何实现函数绘图仪

How to implement a function plotter

我必须使用 OCaml 语言实现一个简单的 2D 函数绘图仪。我明白了。到目前为止,策略是拥有一个函数,我们称之为 plot,它最终会将给定的输入函数 f 映射到 f 的可视化。到目前为止,我认为可视化是一条曲线,它是一组顶点,具有以下 x 和 y 坐标,x 沿水平轴变化,y 为 f(x) 的值。第一步是计算顶点并将其存储在某处,然后继续绘制每个点。只是,为了可视化目的,这是计算和调用几个问题的大量信息的方式:我们要使用什么比率,x 可以有多少个值,要计算多少顶点以及我们需要多少顶点来绘制实际曲线?所以我对绘制函数曲线的适当策略有点迷茫。有人可以给我一些想法或简单的模板,以便我详细说明吗?或给定函数的示例,例如 x^2.

这是我使用 lablgl 和 GLUT 生成的一些代码,因为我也打算实现 3D 绘图仪,它绘制函数 f(x) = sin(x*10) / (1+x^2 ).

open Gl;;
open GlMat;;
open GlDraw;;
open GlClear;;
open Glut;;

(* Transform RGB values in [0.0 - 1.0] to use it with OpenGL *)

let oc = function
    x -> float x /. 255.
;;

(* The function to be graphed *)

let expression = function
    x -> sin (10. *. x) /. (1. +. x *. x)
;;


(* The rendering function drawing 2000 points in 400x400 canvas *)

let display () =
  GlClear.color (oc 255, oc 255,oc 255);
  clear [`color];
  load_identity ();
  begins `lines;
  GlDraw.color (oc 0, oc 0, oc 0);
  List.iter vertex2 [-1.,0.; 1.,0.];
  List.iter vertex2 [0.,-1.;0.,1.];
  ends ();
  begins `points;
  for i=0 to 2000  do
    let  x = (float i -. 1000.) /. 400. in
    let y = expression (x)  in 
    vertex2 (x,y);
  done;
  ends ();
  swapBuffers ();
  flush();
;;

(* general stuff and  main loop *)

let () =
  ignore (init Sys.argv);
  initWindowSize ~w:400 ~h:400;
  initDisplayMode ~double_buffer:true ();
  ignore (createWindow ~title:"Sin(x*10)/(1+x^2)");
  mode `modelview;
  displayFunc ~cb:display;
  idleFunc ~cb:(Some postRedisplay);
  keyboardFunc ~cb:(fun ~key ~x ~y -> if key=27 then exit 0);
  mainLoop ()
;;

提前谢谢大家!

没有简单的方法自动 select X 和 Y 的范围,因为这强烈依赖于函数并且需要真正理解函数行为。 (这在很大程度上也是主观的。)

所以最简单的方法是将其保留为用户可调整的参数,并使用合适的默认值(比如 [-5,5]x[-5,5])。

一个重要的问题是纵横比:您将在称为视口的矩形中绘图;如果此矩形边的比例与函数的 X 和 Y 范围的比例不匹配(这称为 Window),则曲线将变形。圆将显示为椭圆。

视情况而定,是否确保宽高比相等很重要。您应该将其保留为用户选项。如果是,您的软件应调整 Window 限制以适应。

关于点密度,可以采用如下策略:横向选择固定数量的点(比如100)。通过这样的步骤,线段将是可辨别的。然后您可以使用递归细分过程来获得平滑的绘图:考虑一条线段,计算端点的中间点并评估真实曲线点到线段的距离。如果它超过阈值,将片段分成两半并重复两半。请务必在转换为视口坐标后执行计算,以便所有值都以像素为单位。

使用 archimedes 库,您可以像 A.fx vp f 0. 10. 一样简单地绘制函数 f。但是让我们一步一步来,从安装开始。

  1. 通过 opam 安装 archimedes

    opam install archimedes
    
  2. 开始顶层

    ocaml
    
  3. 加载 topfind

    # #use "topfind";;
    
  4. 加载阿基米德库:

    # #require "archimedes";;
    ...
    Module Archimedes loaded and aliased as A.
    
  5. 开始播放:

    let f x = sin (x *. 10.) /. ( 1. +. x**2.);;
    let vp = A.init ["graphics"; "hold"];;
    A.Axes.box vp;;
    A.fx vp f 0. 10.;;
    A.close vp;;
    
  6. 看看你做了什么

这里的伙计们,它实现了一个工作得很好的绘图仪,忽略缩放功能,它真的不起作用,如果有人有提示,我仍然有非法值的问题,我会很高兴!

open Graphics

type vis_zoom = {mutable z:float}

type vis_unit = {mutable au:int; mutable ou:int}

type range = { mutable abs : int * int; mutable ord : int * int}

(* Facteur zoom courrant *)
let czoom = {z = 1.}

(* L'unité graphique courrante en pixels,
   respectivement, pour les abscisse et
   les ordonnees *)
let cunit = { au = 50; ou = 50} 


(* Les rang d'évaluation de la fonction *)
let crange = {abs = (-5,5); ord = (-5,5)}

let fint x = float_of_int x

let ifloat x = int_of_float x

let set_unit u =
  cunit.au <- (fst u);
  cunit.ou <- (snd u)

let set_range amin amax omin omax =
  crange.abs <- (amin, amax);
  crange.ord <- (omin, omax)

let set_zoom z = 
  czoom.z <- z;
  let amin = ifloat ((fint (fst crange.abs)) *. z) and
    amax = ifloat ((fint (snd crange.abs)) *. z) and
    omin = ifloat ((fint (fst crange.ord)) *. z) and
    omax = ifloat ((fint (snd crange.ord)) *. z) in
  crange.abs <- (amin, amax);
  crange.ord <- (omin, omax)


(* Permet de dire si un nombre flotant est nan,
   not a number, ou pas  *)
let is_nan = function x -> (not (x < 0.) && not( x >= 0.))

(* Retourne une liste d'entiers qui représente
   le rang défini par les bornes d'un couple d'entiers r*)

let rec range_list r =
  match r with
    (f,l) when f > l -> []
  | (f,l) -> f:: range_list (f+1,l)

(* Modifie la taille de l'unité graphique
   en fonction de la taille de la fenêtre et
des range d'évaluation *)

let rel_unit a o =
  let x = List.length a and
  y = List.length o in
  let au = (size_x ()) / x and
  ou = (size_y ()) / y in
  set_unit (au,ou)


(* Détérmine les coordonnées graphiques de l'origine 0,0 *)
let make_origin () =
  let zabs = cunit.au/2 - cunit.au * fst crange.abs and
    zord = cunit.ou/2 - cunit.ou * fst crange.ord in
  (zabs,zord)

(* Dessine les graduation d'un axe *)

let rec draw_marks marks xoff yoff xpace ypace zabs zord = match marks with
  |[] -> ()
  |0::t ->
    draw_marks t xoff yoff xpace ypace zabs zord
  |h::t ->
    begin
      let x = zabs + (h * xpace) and
      y = zord + (h * ypace) in
      if
        (0 <= x) && (x <= (size_x ())) &&  (0 <= y) && (y <= (size_y ()))
      then
        begin
          moveto (x - xoff) (y + yoff);
          lineto (x + (1 * xoff)) (y - (1 * yoff));
          moveto (x + (3 * xoff)) (y - (3 * yoff));
          draw_string (string_of_int h);
          draw_marks t xoff yoff xpace ypace zabs zord
        end
      else
        draw_marks t xoff yoff xpace ypace zabs zord
    end

(* Dessine les axes des ordonnées et des abscisses
 avec les graduations *)

let draw_axis () =
  let abs_range = range_list (crange.abs) and
  ord_range = range_list (crange.ord) in
  rel_unit abs_range ord_range;
  let origin = make_origin ()in
  let zabs = fst origin and
    zord = snd origin in
  set_color black;
  moveto 0 zord;
  lineto (size_x ()) zord;
  moveto zabs zord;
  draw_marks abs_range 0 5 cunit.au 0 zabs zord;
  moveto zabs 0;
  lineto zabs (size_y ());
  moveto zabs zord;
  draw_marks ord_range 5 0 0 cunit.ou zabs zord


(* Algroithme de rafinement entre deux point p et p'
   par rapport a l'origine *)

let rec refine x y x' y' zabs zord (f:float -> float)=
  let x'' = ((x' - x) / 2) + x and
  y'' = ((y' - y) / 2) + y and
  f_of_x = f ((fint (x- zabs)) /. (fint cunit.au)) and
  f_of_x' = f ((fint (x'- zabs)) /. (fint cunit.au)) in
  let true_y = zord + (ifloat (f ((fint (x''- zabs)) /. (fint cunit.au)) *. (fint cunit.ou))) and
  f_of_x'' = f (((fint x'') -. (fint zabs)) /. (fint cunit.au)) in 
  if abs (true_y - y'') > 0 && (x''-x) >= 1
  then
    begin
      refine x y x'' true_y zabs zord f;
      refine x'' true_y x' y' zabs zord f;
    end
  else if  not (is_nan f_of_x) && not (is_nan f_of_x') && not (is_nan f_of_x'')
           && (0 <= y) && (y <= (size_y ())) && (0 <= y') && (y' <= (size_y ()))
  then
    begin
      begin
        if
          f_of_x = infinity
        then
          moveto x (size_y ())
        else if
          f_of_x = neg_infinity
        then
          moveto x 0
        else
          moveto x y
      end;
      begin
        if
          f_of_x' = infinity
        then
          lineto x' (size_y ())
        else if
          f_of_x' = neg_infinity
        then
          lineto x' 0
        else
          lineto x' y'
      end;
    end
  else
    ()

(* Dessine la courbe d'une fonction passé en paramètre *)

let rec draw_curve (f:float -> float) =
  set_color blue;
  let rec aux abslist zabs zord  = match abslist with
    |[] -> ()
    |a::b::t ->
      begin
        let x = zabs + (a * cunit.au) and
        y = zord + (ifloat ((f (fint a)) *. (fint cunit.ou))) and
        x' = zabs + (b * cunit.au) and
        y' = zord + (ifloat ((f (fint b)) *. (fint cunit.ou))) in
        refine x y x' y' zabs zord f;
        aux (b::t) zabs zord
      end
    |h::[] -> ()
  and origin = make_origin () in
  let zabs = fst origin and
  zord = snd origin  in
  let  abs = range_list (crange.abs)
  in
  aux abs zabs zord

let zoomin () =
  let z = (czoom.z -. 0.25) in
  set_zoom z

let zoomout () =
  let z = (czoom.z +. 0.25) in
  set_zoom z

let move dx dy =
  let offx = dx / cunit.au and
  offy = dy / cunit.ou in
  let amin = (fst crange.abs) - offx and
    amax = (snd crange.abs) - offx and
    omin = (fst crange.ord) - offy and
    omax = (snd crange.ord) - offy in
  crange.abs <- (amin,amax);
  crange.ord <- (omin,omax)


(* Boucle principale de la courbe pour la déplacer ou zoomer *)

let rec loop f =
  draw_axis ();
  draw_curve f;
  let e = wait_next_event [Key_pressed; Button_down] in
  if e.button then
    let fx = e.mouse_x and
    fy = e.mouse_y and
    se = wait_next_event [Button_up] in
    let sx = se.mouse_x and
      sy = se.mouse_y in
    let dx = sx - fx and
      dy = sy - fy in
    move dx dy;
    clear_graph ();
    draw_axis ();
    draw_curve f;
    loop f
  else if e.keypressed then
    if e.key = 'i'
    then
      begin
        zoomin ();
        clear_graph ();
        draw_axis ();
        draw_curve f;
        loop f
      end
    else if e.key = 'o'
    then
      begin
        zoomout ();
        clear_graph ();
        draw_axis ();
        draw_curve f;
        loop f
      end
    else if e.key = 'q'
    then ()
      else loop f
  else
    loop f

(* Fonction qui ouvre une fenêtre et dessine, les axes
 dans les intervales [amin,amax] et [omin, omax] *)

let gplot (f: float -> float) amin amax omin omax =
  open_graph " 800x600+200-200";
  set_window_title "Plot";
  set_range amin amax omin omax;
  loop f;
  close_graph ()

(* Fonction équivalente a gplot mais dans les intervals
 par défaut [-5,5] et [-5,5] *)

let plotd (f: float -> float) =
  gplot f (-5) 5 (-5) 5