unit uplot;
(*
  Ploteador super-sencillo en canvas subdividido en Canales.

  La idea es que dibuja directamente y no mantiene información como para
  re-dibujar a diferencia de la implementación de ugraficador.pas

*)

{$mode delphi}

interface

uses
  Classes, SysUtils, Graphics, math, xmatdefs, matreal,
  isocurvas,
  uescalacolores;

const
  colores: array[ 0 .. 15 ] of TColor = (
  clBlack,
  clMaroon,
  clGreen,
  clOlive,
  clNavy,
  clPurple,
  clTeal,
  clGray,
  clSilver,
  clRed,
  clLime,
  clYellow,
  clBlue,
  clFuchsia,
  clAqua,
  clWhite );



type
  TSerieXY = class;
  TSerieXYZ = class;
  TGraficas = class;

  { TEscalaR }

  TEscalaR = class
    r1, r2: NReal;
    dr: NReal;
    flg_Degenerada: boolean;
    constructor Create( r1_, r2_: NReal );
    procedure SetRango( r1_, r2_: NReal );
    function rTopu( r: NReal ): NReal;
    function puTor(u: NReal): NReal;
  end;

  { TCanalGrafico }
  TCanalGrafico = class
    graficas: TGraficas;
    kColorAuto: integer;
    dy_xlabel, dx_ylabel,  dx_ylabel2: integer;
    dx_etiquetasy, dy_etiquetasx, dx_etiquetasy2: integer;
    dy_titulo: integer;
    fontsize_etiquetas: integer;
    fontsize_xylabel: integer;
    fontsize_titulo: integer;
    dx_sep, dy_sep: integer;

    txp1, typ1, txp2, typ2: integer;   // valores de creación total ventana.
    tdxp, tdyp: integer;
    xp1, yp1, dxp, dyp: integer;
    canal_clipRect: TRect;
    series_clipRect: TRect;
    series: TList;

    title_str: string;
    xlabel_str: string;
    ylabel_str: string;
    ylabel2_str: string;
    etiquetasx_str: string;
    etiquetasy_str: string;
    etiquetasy2_str: string;

    nDivsX, nDivsY, nDivsY2: integer;
    xmin, xmax: NReal; // para las etiquetas
    ymin, ymax: NReal; // para las etiquetas el eje Y Principal
    ymin2, ymax2: NReal; // para las etiquetas del eje secundario

    showEtiquetasX, ShowEtiquetasY, ShowEtiquetasY2: boolean;

    showGridX, showGridY: boolean;
    nSubGridX, nSubGridY, nSubGridY2: integer;
    format_x, format_y, format_y2: string;

    constructor Create(aGraficas: TGraficas; xp1, yp1, xp2, yp2: integer);

    procedure Title( titulo: String );
    procedure xlabel( xlabel: string );
    procedure ylabel( ylabel: string );
    procedure ylabel2( ylabel2: string );

    procedure EjeX(xmin, xmax: NReal; nDivs: integer; unidades: string;
      format_: string; ShowEtiquetas: boolean; ShowGrid: boolean;
  nSubGrid: integer);
    procedure EjeY( ymin, ymax: NReal; nDivs: integer;
      unidades:string;
      format_: string;
      ShowEtiquetas: boolean;
      ShowGrid: boolean; nSubGrid: integer );
    procedure EjeY2( ymin2, ymax2: NReal; nDivs2: integer;
            unidades: string;
            format_: string;
            ShowEtiquetas: boolean; nSubGrid: integer);

    // Dibuja la caja de las series
    procedure CajaSeries;

    // Dibuja los títulos y grillas.
    procedure TitulosYGrillas;

    function AddSerieXY( nombreSerie: string  ): TSerieXY;
    function AddSerieXYZ( nombreSerie: string  ): TSerieXYZ;
    function NextColor: TColor;


    // si r > 0 result = r+ sep else result = 0
    function sisep( r, sep: integer ): integer;
    destructor destroy; override;
  private
    procedure fijarVentana;
    procedure Draw_Title;
    procedure Draw_xlabel;
    procedure Draw_ylabel;
    procedure Draw_ylabel2;
    procedure Draw_etiquetasx;
    procedure Draw_etiquetasy;
    procedure Draw_etiquetasy2;
    procedure Draw_GridX;
    procedure Draw_GridY;
  end;

  { TSerieXY }
  TSerieXY = Class
    canal: TCanalGrafico;
    nombre: string;
    pen_color: TColor;
    pen_width: integer;

    escalaX, escalaY: TEscalaR;

    constructor Create( canal: TCanalGrafico; nombre: string );
    procedure SetEscalaX(  xmin, xmax: NReal );
    procedure SetEscalaY(  ymin, ymax: NReal );

    function xpOf( x: NReal ): integer;
    function ypOf( y: NReal ): integer;

    function xOf( xp: integer ): NReal;
    function yOf( yp: integer ): NReal;

    procedure go( x, y: NReal );
    procedure lineTo( x, y: NReal );
    procedure line( x1, y1, x2, y2: NReal );

    procedure text( x, y: NReal; s: string );

    // fija el color y ancho del lapiz
    procedure activar;

    // dx, dy en por unidad de las escalas.
    // radio enpor unidad del total del canal. 1 = ancho del canal.
    procedure flecha(x, y, dx, dy: NReal; color: TColor; radio: NReal);

    destructor destroy; override;
  end;


  { TSerieXYZ }

  TSerieXYZ = Class
    canal: TCanalGrafico;
    nombre: string;
    pen_color: TColor;
    pen_width: integer;

    escalaX, escalaY, escalaZ: TEscalaR;
    cos_angy, sin_angy: NReal;

    constructor Create( canal: TCanalGrafico; nombre: string );
    procedure SetEscalaX(  xmin, xmax: NReal );
    procedure SetEscalaY(  ymin, ymax: NReal );
    procedure SetEscalaZ(  zmin, zmax: NReal );

    procedure xpypOf( out xp, yp: integer;  x,y,z: NReal );

    procedure go( x, y, z: NReal );
    procedure lineTo( x, y, z: NReal );
    procedure line( x1, y1, z1, x2, y2, z2: NReal );

    // fija el color y ancho del lapiz
    procedure activar;

    destructor destroy; override;
  end;



  { TGraficas }

  TGraficas = class
    width, height: integer;
    canvas: TCanvas;
    canales: TList;

    ytag: integer; // alto del título y de la escala X incluyendo leyendas
    xtag: integer; // ancho del eje Y1 y del eje Y2 incluyendo leyendas
    nRows, nCols: integer;

    fondo_Color: TColor; // fondo del canal
    textoycaja_Color: TColor; // lineas y texto

    constructor Create;
    procedure subplot(aCanvas: TCanvas; canvas_width, canvas_height: integer;
      NRows, NCols: integer; ProporcionesCols, ProporcionesRows: TDAofNReal;
  flg_clear: boolean);
    function getCanal( kRow, kCol: integer ): TCanalGrafico;
    destructor Destroy; override;

    // borra todo pintando con el color del fondo.
    procedure Clear;
  end;


function ColorGradienteAzulRojo( r: nreal ): TColor;
function ColorGradienteAzulVerdeRojo( r: nreal ): TColor;

procedure PlotCurvasIsoNivel( c: TSerieXY;  m: TMatR; vx, vy: TVectR; niveles: TDAOfNReal; flg_ConEtiquetas: boolean );


implementation


type

  { TPuntoXY }

  TPuntoXY = class
    x, y: NReal;
    constructor Create( x, y : NReal );
  end;

procedure PlotCurvasIsoNivel(c: TSerieXY; m: TMatR; vx, vy: TVectR;
  niveles: TDAOfNReal; flg_ConEtiquetas: boolean);
var
  knivel: integer;
  idPol: integer;
  IsoScanner: TIsoScannerMatricial;
  color: TColor;
  ramaDirecta: boolean;
  plabel_x, plabel_y: NReal;
  res: boolean;
  px, py: NReal;
  pol: TList;
  aPunto: TPuntoXY;
  k: integer;

begin
  pol:= TList.Create;

  for knivel := 0 to high(niveles) do
  begin
    IsoScanner := TIsoScannerMatricial.Create( m, vx, vy, niveles[knivel]);

    ramaDirecta := True;
    plabel_y := c.escalaY.r2;

    repeat
      res := IsoScanner.GetPunto(px, py);  {pedimos un punto a iso90}
      if IsoScanner.Estado = Reversa then
        ramaDirecta := not ramaDirecta;

      if res then   {Si el punto es valido lo dibuja}
      begin
        if py < plabel_y then   {elige el punto más bajo}
        begin
          plabel_x := px;
          plabel_y := py;          {para poner la etiqueta}
        end;
        if ramaDirecta then
          pol.add( TPuntoXY.Create(px, py) )
        else
          pol.Insert(0, TPuntoXY.Create( px, py ) );
      end;

      if IsoScanner.Estado = Semiactivo then   {termino con una curva}
      begin
        aPunto:= pol[0];
        c.go( aPunto.x, aPunto.y );
        aPunto.Free;
        for k:= 1 to pol.count -1 do
        begin
          aPunto:= pol[k];
          c.lineto( aPunto.x, aPunto.y );
          aPunto.Free;
        end;
        pol.Clear;
        ramaDirecta := True;
        if flg_ConEtiquetas then
           c.text( plabel_x, plabel_y, FloatToStr( niveles[kNivel] ) );
        plabel_y := c.escalaY.r2;
      end;
    until IsoScanner.Estado = fin;       {hasta terminar}
    IsoScanner.Free;
  end;
  pol.Free;
end;


function ColorGradienteAzulRojo(r: nreal): TColor;
begin
  if r < 0 then r:= 0
  else if r > 1 then r:= 1;
  result:= RGBToColor( trunc( 255* r ), 0, trunc( 255 * (1-r ) ) );
end;

function ColorGradienteAzulVerdeRojo(r: nreal): TColor;
var
  alfa, beta: NReal;
begin
  alfa:= 2* r * (1-r );
  beta:= 1-alfa;
  result:= RGBToColor( trunc( 255* r * beta ), trunc( 255 * alfa), trunc( 255 * (1-r )*beta ) );
end;

{ TPuntoXY }

constructor TPuntoXY.Create(x, y: NReal);
begin
  self.x:= x;
  self.y:= y;
end;

{ TSerieXYZ }

constructor TSerieXYZ.Create(canal: TCanalGrafico; nombre: string);
begin
  Self.Nombre:= nombre;
  Self.canal:= Canal;
  pen_color:= Canal.NextColor;
  pen_width:= 1;
  EscalaX:= TEscalaR.Create( 0, 1 );
  EscalaY:= TEscalaR.Create( 0, 1 );
  EscalaZ:= TEscalaR.Create( 0, 1 );
  cos_angy:= cos(  pi * 30.0/180.0 );
  sin_angy:= sin(  pi * 30.0/180.0 );

end;

procedure TSerieXYZ.SetEscalaX(xmin, xmax: NReal);
begin
  escalaX.setRango( xmin, xmax );
end;

procedure TSerieXYZ.SetEscalaY(ymin, ymax: NReal);
begin
  escalaX.setRango( ymin, ymax );
end;

procedure TSerieXYZ.SetEscalaZ(zmin, zmax: NReal);
begin
  escalaX.setRango( zmin, zmax );
end;

procedure TSerieXYZ.xpypOf(out xp, yp: integer; x, y, z: NReal);
var
  vx, vy, dy: NReal;
begin
  dy:= 0; //EscalaY.rpu( y );
  vx:= x + dy * cos_angy;
  vy:= y + dy * sin_angy;

  xp:= round( canal.xp1 + canal.dxp * EscalaX.rTopu( vx ) );
  yp:= round( canal.yp1 + canal.dyp * ( 1.0 - EscalaY.rTopu( vy ) ));
end;

procedure TSerieXYZ.go(x, y, z: NReal);
var
  xp, yp: integer;
begin
  xpypOf( xp, yp, x, y, z );
  canal.graficas.canvas.MoveTo( xp, yp );
end;

procedure TSerieXYZ.lineTo(x, y, z: NReal);
var
  xp, yp: integer;
begin
  xpypOf( xp, yp, x, y, z );
  canal.graficas.canvas.LineTo(xp, yp);
end;

procedure TSerieXYZ.line(x1, y1, z1, x2, y2, z2: NReal);
var
  xp1, yp1: integer;
  xp2, yp2: integer;
begin
  xpypOf( xp1, yp1, x1, y1, z1 );
  xpypOf( xp2, yp2, x2, y2, z2 );
  canal.graficas.canvas.line( xp1, yp1, xp2, yp2 );
end;

procedure TSerieXYZ.activar;
begin
  canal.graficas.canvas.Pen.Color:= pen_color;
  canal.graficas.canvas.Pen.Width:= pen_width;
  canal.graficas.canvas.ClipRect:= canal.series_clipRect;
end;

destructor TSerieXYZ.destroy;
begin
  escalaX.Free;
  escalaY.Free;
  escalaZ.Free;
  inherited destroy;
end;

{ TEscalaR }

constructor TEscalaR.Create(r1_, r2_: NReal);
begin
  inherited Create;
  setRango( r1_, r2_ );
end;

procedure TEscalaR.SetRango(r1_, r2_: NReal);
begin
  r1:= r1_;
  r2:= r2_;
  dr:= r2 - r1;
  if abs( dr ) <= AsumaCero then
   flg_Degenerada:= true
  else
    flg_Degenerada:=false;;
end;

function TEscalaR.rTopu( r: NReal): NReal;
begin
  if flg_Degenerada then
   result:= 0
  else
   result:= (r-r1)/dr;
end;

function TEscalaR.puTor( u: NReal): NReal;
begin
  if flg_Degenerada then
    result:= r1
  else
    result:= r1 + dr * u;
end;


{ TGraficas }

constructor TGraficas.Create;
begin
  inherited Create;
  canvas:= nil;
  canales:= Tlist.Create;
  fondo_Color:= clWhite;
  textoycaja_Color:= clBlack;

end;

procedure TGraficas.subplot(
  aCanvas: TCanvas;
  canvas_width, canvas_height: integer;
  NRows, NCols: integer;
  ProporcionesCols, ProporcionesRows: TDAofNReal;
  flg_clear: boolean );
var
  k: integer;
  aCanal: TCanalGrafico;
  wt, ht: integer;
  xp1, yp1, xp2, yp2: integer;
  ppAcumx1, ppAcumy1: NReal;
  ppAcumx2, ppAcumy2: NReal;
  kRow, kCol: integer;

begin
  self.nRows:= NRows;
  self.nCols:= nCols;
  self.canvas:= aCanvas;
  self.width:= canvas_width;
  self.height:= canvas_height;
  if ProporcionesCols = nil then
  begin
   setlength( ProporcionesCols, nCols );
   for k:= 0 to high( ProporcionesCols ) do
     ProporcionesCols[k]:= 1/ nCols;
  end;

  if ProporcionesRows = nil then
  begin
   setlength( ProporcionesRows, nRows );
   for k:= 0 to high( ProporcionesRows ) do
     ProporcionesRows[k]:= 1/ nRows;
  end;


  if canales.Count > 0 then
  begin
    for k:= 0 to Canales.Count -1 do
    begin
      aCanal:= canales[k];
      aCanal.Free;
    end;
    canales.Clear;
  end;

  wt:= canvas_Width;
  ht:= canvas_Height;

  canvas.GetTextSize( 'M', ytag, xtag );
  xtag:= 2* xtag;
  ytag:= 2* ytag;

  ppAcumy1:= 0;
  for kRow:= 0 to NRows - 1 do
  begin
    ppAcumy2:= ppAcumy1 + ProporcionesRows[kRow];
    yp1:= trunc( ht * ppAcumy1 );
    yp2:= trunc( ht * ppAcumy2 );
    ppAcumx1:= 0;
    for kCol:= 0 to NCols - 1 do
    begin
      ppAcumx2:= ppAcumx1 + ProporcionesCols[kCol];
      xp1:= trunc( wt * ppAcumx1 );
      xp2:= trunc( wt * ppAcumx2 );
      aCanal:= TCanalGrafico.Create( self, xp1, yp1, xp2, yp2 );
      canales.Add( aCanal );
      ppAcumx1:= ppAcumx2;
    end;
    ppAcumy1:= ppAcumy2;
  end;

  if flg_clear then
    clear;
end;

function TGraficas.getCanal(kRow, kCol: integer): TCanalGrafico;
begin
  result:= canales[ kRow* nCols + kCol ];
end;

destructor TGraficas.Destroy;
begin
  inherited Destroy;
end;

procedure TGraficas.Clear;
begin
  exit;
  canvas.ClipRect:= TRect.Create( 0, 0, width, height );
  canvas.Brush.Color:= fondo_Color;
  canvas.FillRect( 0, 0, width, height );
end;

{ TCanalGrafico }

constructor TCanalGrafico.Create(aGraficas: TGraficas; xp1, yp1, xp2,
  yp2: integer);
begin
  self.graficas:= aGraficas;
  kColorAuto:= 0;
  dy_xlabel:= 0;
  dx_ylabel:= 0;
  dx_ylabel2:= 0;
  dx_etiquetasy:= 0;
  dy_etiquetasx:= 0;
  dx_etiquetasy2:= 0;
  dy_titulo:= 0;
  fontsize_titulo:= round( 14 * 1.5 );
  fontsize_etiquetas:= round( 14 * 0.5 );
  fontsize_xylabel:= round( 14 * 0.7 );;
  dx_sep:= 4;
  dy_sep:= 4;
  txp1:= xp1;
  typ1:= yp1;
  txp2:= xp2;
  typ2:= yp2;
  title_str:= '';
  xlabel_str:= '';
  ylabel_str:= '';
  ylabel2_str:= '';
  etiquetasx_str:= ''; // unidades
  etiquetasy_str:= '';
  etiquetasy2_str:= '';
  fijarVentana;
  series:= TList.Create;
end;

procedure TCanalGrafico.fijarVentana;
var
  pretag: integer;

begin
  self.tdxp:= txp2 - txp1;
  self.tdyp:= typ2- typ1;

  pretag:= ( sisep( dx_ylabel, dx_sep) + sisep( dx_etiquetasy, dx_sep ));
  self.xp1:= txp1 + pretag;
  self.dxp:= tdxp
    - pretag
    -( sisep( dx_ylabel2, dx_sep ) + sisep( dx_etiquetasy2, dx_sep ));

  pretag:= sisep( dy_titulo, dy_sep );
  self.yp1:= typ1 + pretag;
  self.dyp:= tdyp
    - pretag
    - sisep( dy_etiquetasx, dy_sep )
    - sisep( dy_xlabel, dy_sep );

  canal_clipRect:= TRect.Create( txp1, typ1, txp1 + tdxp, typ1 + tdyp );
  series_clipRect:= TRect.Create( xp1, yp1, xp1 + dxp, yp1 + dyp );

end;

procedure TCanalGrafico.Title(titulo: String);
var
  xp, yp: integer;
begin
  title_str:= titulo;
  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_titulo;
  dy_titulo:= graficas.Canvas.TextHeight( title_str );
end;

procedure TCanalGrafico.Draw_Title;
var
  xp, yp: integer;
begin
  if title_str = '' then exit;
  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_titulo;
  xp:= xp1 + ( dxp - graficas.canvas.GetTextWidth( title_str ) ) div 2;
  yp:= typ1;
  graficas.canvas.ClipRect:= canal_clipRect;
  graficas.canvas.TextOut( xp, yp, title_str );
end;


procedure TCanalGrafico.xlabel( xlabel: string);
begin
   xlabel_str:= xlabel;
   graficas.canvas.Font.Orientation:= 0;
   graficas.canvas.Font.Size:= fontsize_xylabel;
   dy_xlabel:= graficas.Canvas.TextHeight( xlabel_str );
end;

procedure TCanalGrafico.Draw_xlabel;
var
  xp, yp: integer;
begin
  if xlabel_str = '' then exit;
  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.size:= fontsize_xylabel;
  xp:= xp1 + ( dxp - graficas.canvas.GetTextWidth( xlabel_str ) ) div 2;

  yp:= typ2 - graficas.canvas.GetTextHeight( xlabel_str );
  graficas.canvas.ClipRect:= canal_clipRect;
  graficas.canvas.TextOut( xp, yp, xlabel_str );
end;


procedure TCanalGrafico.ylabel( ylabel: string);
begin
  ylabel_str:= ylabel;
  graficas.canvas.Font.Orientation := 900;
  graficas.canvas.Font.Size:= fontsize_xylabel;
  dx_ylabel:= graficas.canvas.GetTextHeight( ylabel_str );
end;

procedure TCanalGrafico.ylabel2(ylabel2: string);
begin
  ylabel2_str:= ylabel2;
  graficas.canvas.Font.Size:= fontsize_xylabel;
  graficas.canvas.Font.Orientation := 900;
  dx_ylabel2:= graficas.canvas.GetTextHeight( ylabel_str );
end;


procedure TCanalGrafico.Draw_ylabel;
var
  xp, yp: integer;
begin
  graficas.canvas.Font.Orientation := 900;
  graficas.canvas.Font.Size:= fontsize_xylabel;
  xp:= txp1;
  yp:= yp1   + dyp - ( dyp - graficas.canvas.GetTextHeight( ylabel_str ) ) div 2;
  graficas.canvas.ClipRect:= canal_clipRect;
  graficas.canvas.TextOut( xp, yp, ylabel_str );
  graficas.canvas.Font.Orientation := 0;
end;

procedure TCanalGrafico.Draw_ylabel2;
var
  xp, yp: integer;
begin
  graficas.canvas.Font.Orientation := 900;
  graficas.canvas.Font.Size:= fontsize_xylabel;


  xp:= xp1 + dxp + sisep( dx_etiquetasy2, dx_sep ) + dx_sep;
  yp:= yp1 + dyp - ( dyp - graficas.canvas.GetTextHeight( ylabel2_str ) ) div 2;
  graficas.canvas.ClipRect:= canal_clipRect;
  graficas.canvas.TextOut( xp, yp, ylabel2_str );
  graficas.canvas.Font.Orientation := 0;
end;




procedure TCanalGrafico.CajaSeries;
begin
  graficas.canvas.Brush.Color:= graficas.fondo_Color;
  graficas.canvas.Pen.Color := graficas.fondo_Color;;
  graficas.canvas.Rectangle( canal_clipRect );
  graficas.canvas.Pen.Color := graficas.textoycaja_Color;
  fijarVentana;
  graficas.canvas.Brush.Color:= graficas.fondo_Color;
  graficas.canvas.Pen.Color := graficas.textoycaja_Color;
  graficas.canvas.Rectangle( series_clipRect );
end;

procedure TCanalGrafico.TitulosYGrillas;
begin
  Draw_title;
  Draw_xlabel;
  Draw_ylabel;
  Draw_ylabel2;
  Draw_etiquetasx;
  Draw_etiquetasy;
  Draw_etiquetasy2;
  Draw_GridX;
  Draw_GridY;
end;

procedure TCanalGrafico.Draw_GridX;
var
  xp: integer;
  k, j: integer;
begin
  if not showGridX then exit;
  graficas.canvas.Pen.Style:= psDot;
  graficas.canvas.Pen.color:= clLtGray;

  for k:= 1 to nDivsX - 1 do
  begin
    xp:= round( xp1 + k * dxp / nDivsX );
    graficas.canvas.line( xp, yp1, xp, yp1+dyp );
  end;

  if nSubGridX > 0 then
  begin
    graficas.canvas.Pen.Style:= psDashDotDot;
    graficas.canvas.Pen.color:= clLtGray;
    for k:= 1 to nDivsX - 1 do
    begin
      for j:= 1 to nSubGridX do
      begin
        xp:= round( xp1 + k * dxp / nDivsX + j * dxp / nDivsX / ( nSubGridX +1 )  );
        graficas.canvas.line( xp, yp1, xp, yp1+dyp );
      end;
    end;
  end;


  graficas.canvas.Pen.Style:= psSolid;
  graficas.canvas.Pen.color:= graficas.textoycaja_Color;
end;

procedure TCanalGrafico.Draw_GridY;
var
  yp: integer;
  k, j: integer;
begin
  if not showGridY then exit;
  graficas.canvas.Pen.Style:= psDot;
  graficas.canvas.Pen.color:= clLtGray;

  for k:= 1 to nDivsY - 1 do
  begin
    yp:= round( yp1 + k * dyp / nDivsY );
    graficas.canvas.line( xp1, yp, xp1+dxp, yp );
  end;

  if nSubGridY > 0 then
  begin
    graficas.canvas.Pen.Style:= psDashDotDot;
    graficas.canvas.Pen.color:= clLtGray;
    for k:= 1 to nDivsY - 1 do
    begin
      for j:= 1 to nSubGridX do
      begin
        yp:= round( yp1 + k * dyp / nDivsY2+ j * dyp / nDivsY / ( nSubGridY +1 )   );
        graficas.canvas.line( xp1, yp, xp1+dxp, yp );
      end;
    end;
  end;

  graficas.canvas.Pen.Style:= psSolid;
  graficas.canvas.Pen.color:= graficas.textoycaja_Color;
end;

procedure TCanalGrafico.EjeX(xmin, xmax: NReal; nDivs: integer;
  unidades: string; format_: string; ShowEtiquetas: boolean; ShowGrid: boolean;
  nSubGrid: integer);
var
  x: NReal;
  k: integer;
  xp: integer;
  s: string;

begin
  etiquetasx_str:= unidades;
  self.xmin:= xmin;
  self.xmax:= xmax;
  self.nDivsX:= nDivs;
  format_x:= format_;
  self.showEtiquetasX:= ShowEtiquetas;
  self.showGridX:= ShowGrid;
  self.nSubGridX:= nSubGrid;
  dy_etiquetasx:= 0;

  if Not ShowEtiquetas then exit;

  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_etiquetas;
  dy_etiquetasx:= max( dy_etiquetasx, graficas.canvas.TextHeight( unidades ) );

  graficas.canvas.Font.Orientation:= 900;
  for k:= 1 to nDivs - 1 do
  begin
    x:= xmin + k*(xmax - xmin ) / nDivs;
    s:= Format( format_, [x] );
//    s:= FloatToStrF( x, ffFixed, 12, 2 );
    dy_etiquetasx:= max( dy_etiquetasx, graficas.canvas.TextWidth( s ) );
  end;
end;


procedure TCanalGrafico.Draw_etiquetasx;
var
  x: NReal;
  k: integer;
  xp: integer;
  s: string;
  dxc: NReal;
  dys: integer;
begin
  if not showEtiquetasX then exit;
  if dy_etiquetasx = 0 then exit;
  graficas.canvas.Font.Orientation:= 900;
  graficas.canvas.Font.Size:= fontsize_etiquetas;

  graficas.canvas.Font.Color:= graficas.textoycaja_Color;
  graficas.canvas.Pen.Style:=psSolid;
  graficas.canvas.Pen.Color:= graficas.textoycaja_Color;

  dxc:= graficas.canvas.TextHeight('0')/2.0;
  for k:= 1 to nDivsX - 1 do
  begin
    xp:= round( xp1 + k * dxp / nDivsX - dxc );
    x:= xmin + k*(xmax - xmin ) / nDivsX;
    s:= Format( format_x, [x] );
    dys:= graficas.canvas.textwidth( s );
    graficas.canvas.TextOut( xp, yp1 + dyp + dy_sep + dys, s );
    graficas.canvas.MoveTo( round( xp + dxc ) , yp1 + dyp - 4 );
    graficas.canvas.LineTo( round( xp + dxc ), yp1 + dyp );
//    graficas.canvas.TextOut( xp, yp1 + dyp + dy_sep + dy_etiquetasx, s );
  end;

  graficas.canvas.Font.Orientation:= 0;
  xp:= xp1 +dxp - graficas.canvas.TextWidth( etiquetasx_str );
  graficas.canvas.TextOut( xp, yp1 + dyp + dy_sep, etiquetasx_str );

end;


procedure TCanalGrafico.EjeY(ymin, ymax: NReal; nDivs: integer;
  unidades: string; format_: string; ShowEtiquetas: boolean; ShowGrid: boolean;
  nSubGrid: integer);
var
  k: integer;
  y: NReal;
  yp: integer;
  s: string;
begin
  self.ymin:= ymin;
  self.ymax:= ymax;
  self.nDivsY:= nDivs;
  format_y:= format_;
  self.etiquetasy_str:= unidades;
  self.ShowEtiquetasY:= ShowEtiquetas;
  self.showGridY:= ShowGrid;
  self.nSubGridY:= nSubGrid;
  dx_etiquetasy:= 0;
  if not ShowEtiquetas then exit;

  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_etiquetas;

  dx_etiquetasy:= max( dx_etiquetasy,  graficas.Canvas.TextWidth( unidades ) );
  for k:= 1 to nDivs - 1 do
  begin
    yp:= round( yp1 + dyp - k * dyp / nDivs );
    y:= ymin + k*(ymax - ymin ) / nDivs;
    s:= format( format_y, [y] );
  //  s:= FloatToStrF( y, ffFixed, 12, 2 );
    dx_etiquetasy:= max( dx_etiquetasy, graficas.Canvas.TextWidth( s ) );
  end;
end;


procedure TCanalGrafico.Draw_etiquetasy;
var
  k: integer;
  y: NReal;
  yp: integer;
  s: string;
  dyc: NReal;
  dxs: integer;
begin
  if not ShowEtiquetasY then exit;

  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_etiquetas;
  graficas.canvas.Font.Color:= graficas.textoycaja_Color;
  graficas.canvas.Pen.Style:=psSolid;
  graficas.canvas.Pen.Color:= graficas.textoycaja_Color;

  yp:= yp1;

  dxs:= graficas.canvas.TextWidth( etiquetasy_str );
  graficas.canvas.TextOut( xp1 -dxs -dx_sep, yp, etiquetasy_str );

  dyc:= graficas.canvas.TextHeight('0')/2.0;


  for k:= 1 to nDivsY - 1 do
  begin
    yp:= round( yp1 + dyp - k * dyp / nDivsY -dyc );
    y:= ymin + k*(ymax - ymin ) / nDivsY;
    s:= format( format_y, [y] );
    dxs:= graficas.canvas.TextWidth( s );
    graficas.canvas.TextOut( xp1  -dxs -dx_sep, yp, s );
    graficas.canvas.MoveTo( xp1 , round( yp + dyc ) );
    graficas.canvas.LineTo( xp1 + 8, round( yp + dyc ) );

//    graficas.canvas.TextOut( xp1  -dx_etiquetasy -dx_sep, yp, s );
  end;
end;


procedure TCanalGrafico.EjeY2(ymin2, ymax2: NReal; nDivs2: integer;
  unidades: string; format_: string; ShowEtiquetas: boolean; nSubGrid: integer);
var
  k: integer;
  y: NReal;
  yp: integer;
  s: string;
begin
  self.ymin2:= ymin2;
  self.ymax2:= ymax2;
  self.nDivsY2:= nDivs2;
  format_y2:= format_;
  self.etiquetasy2_str:= unidades;
  self.ShowEtiquetasY2:= ShowEtiquetas;
  self.nSubGridY2:= nSubGrid;
  dx_etiquetasy2:= 0;
  if not ShowEtiquetas then exit;

  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_etiquetas;

  dx_etiquetasy2:= graficas.Canvas.TextWidth( unidades );
  for k:= 1 to nDivs2 - 1 do
  begin
    yp:= round( yp1 + dyp - k * dyp / nDivs2 );
    y:= ymin2 + k*(ymax2 - ymin2 ) / nDivs2;
    s:= Format( format_y2, [ y ] );
    dx_etiquetasy2:= max( dx_etiquetasy2, graficas.Canvas.TextWidth( s ) );
  end;
end;


procedure TCanalGrafico.Draw_etiquetasy2;
var
  k: integer;
  y: NReal;
  xp, yp: integer;
  s: string;
  dyc: NReal;

begin
  if not ShowEtiquetasY2 then exit;
  graficas.canvas.Font.Orientation:= 0;
  graficas.canvas.Font.Size:= fontsize_etiquetas;
  graficas.canvas.Font.Color:= graficas.textoycaja_Color;
  graficas.canvas.Pen.Style:=psSolid;
  graficas.canvas.Pen.Color:= graficas.textoycaja_Color;

  yp:= yp1;
  xp:= xp1 +dxp + dx_sep;
  graficas.canvas.TextOut( xp, yp, etiquetasy2_str );

  dyc:= graficas.canvas.TextHeight('0')/2.0;

  for k:= 1 to nDivsY2 - 1 do
  begin
    yp:= round( yp1 + dyp - k * dyp / nDivsY2 -dyc );
    y:= ymin2 + k*(ymax2 - ymin2 ) / nDivsY2;
    s:= Format( format_y2, [ y ] );
    graficas.canvas.TextOut( xp, yp, s );
    graficas.canvas.MoveTo( xp1+dxp-8, round( yp + dyc ) );
    graficas.canvas.LineTo( xp1+dxp, round( yp + dyc ) );
  end;
end;


function TCanalGrafico.AddSerieXY(nombreSerie: string): TSerieXY;
var
  k: Integer;
begin
  k:= series.Add( TSerieXY.Create( self, nombreSerie ) );
  result:= series[k];
end;

function TCanalGrafico.AddSerieXYZ(nombreSerie: string): TSerieXYZ;
var
  k: Integer;
begin
  k:= series.Add( TSerieXYZ.Create( self, nombreSerie ) );
  result:= series[k];
end;

function TCanalGrafico.NextColor: TColor;
begin
  result:= Colores[ kColorAuto ];
  kColorAuto := ( kColorAuto + 1 ) mod 16;
end;

procedure TSerieXY.flecha(x, y, dx, dy: NReal; color: TColor;
  radio: NREal);
var
  xp, yp: integer;
  xp1, yp1: NReal;
  dxn, dyn: Nreal;
  rx, ry: NReal;
  rr, r: NReal;
  graficas: TGraficas;
begin
  graficas:= canal.graficas;
  graficas.canvas.Pen.Style:= psSolid;
  graficas.canvas.Pen.color:= color;

  rx:= dx * canal.dxp * radio;
  ry:= dy * canal.dyp * radio;
  rr:= sqrt( sqr( rx ) + sqr( ry ) );

  dxn:= -dy; //* canal.dxp;
  dyn:= dx; // * canal.dyp;
  r:= sqrt( sqr( dxn ) + sqr( dyn ) );
  dxn:= dxn / r;
  dyn:= dyn / r;


  xp:= xpOf( x );
  yp:= ypOf( y );


  xp1:= xp + rx*( 1 - 1/rr*8);
  yp1:= yp - ry*(1- 1/rr*8);

  graficas.canvas.MoveTo( xp, yp );
  graficas.canvas.LineTo( round( xp + rx ), round( yp - ry ));
                       (*
  graficas.canvas.Pen.color:= clBlack;
  graficas.canvas.LineTo( round(xp1), round(yp1)  );
  graficas.canvas.Pen.color:= clWhite;
                         *)
  graficas.canvas.LineTo( round( xp1+ dxn * 4 ),  round( yp1 - dyn * 4 )  );
  graficas.canvas.MoveTo( round( xp + rx ), round( yp - ry ));
  graficas.canvas.LineTo( round( xp1 - dxn * 4 ),  round( yp1 + dyn * 4 )  );
end;

function TCanalGrafico.sisep(r, sep: integer): integer;
begin
  if r > 0 then
    result:= r + sep
  else
    result:= 0;
end;

destructor TCanalGrafico.destroy;
var
  k: integer;
  aSerie: TSerieXY;
begin
  for k:= 0 to series.count-1 do
  begin
    aSerie:= series[k];
    aSerie.Free;
  end;
  series.Free;
  inherited destroy;
end;

{ TSerieXY }

constructor TSerieXY.Create(canal: TCanalGrafico; nombre: string);
begin
  Self.Nombre:= nombre;
  Self.canal:= Canal;
  pen_color:= Canal.NextColor;
  pen_width:= 1;
  EscalaX:= TEscalaR.Create( 0, 1 );
  EscalaY:= TEscalaR.Create( 0, 1 );
end;

procedure TSerieXY.SetEscalaX(xmin, xmax: NReal);
begin
  escalaX.setRango( xmin, xmax );

end;

procedure TSerieXY.SetEscalaY(ymin, ymax: NReal);
begin
  escalaY.setRango( ymin, ymax );
end;


function TSerieXY.xpOf(x: NReal): integer;
begin
  result:= round( canal.xp1 + canal.dxp * EscalaX.rTopu( x ) );
end;

function TSerieXY.ypOf(y: NReal): integer;
begin
  result:= round( canal.yp1 + canal.dyp * ( 1.0 - EscalaY.rTopu( y ) ));
end;

function TSerieXY.xOf(xp: integer): NReal;
begin
  result:= EscalaX.puTor(( xp - canal.xp1 )/ canal.dxp );
end;

function TSerieXY.yOf(yp: integer): NReal;
begin
  result:= EscalaY.puTor( 1-( yp - canal.yp1 )/ canal.dyp );
end;

procedure TSerieXY.go(x, y: NReal);
begin
  canal.graficas.canvas.MoveTo( xpOf( x ), ypOf( y ) );
end;

procedure TSerieXY.lineTo(x, y: NReal);
begin
  canal.graficas.canvas.LineTo( xpOf( x ), ypOf( y ) );
end;

procedure TSerieXY.line(x1, y1, x2, y2: NReal);
begin
  canal.graficas.canvas.Line( xpOf( x1 ), ypOf( y1 ), xpOf( x2 ), ypOf( y2 ));
end;

procedure TSerieXY.text(x, y: NReal; s: string);
begin
  canal.graficas.canvas.TextOut( xpOf(x), ypOf(y), s );
end;

procedure TSerieXY.activar;
begin
  canal.graficas.canvas.Pen.Color:= pen_color;
  canal.graficas.canvas.Pen.Width:= pen_width;
  canal.graficas.canvas.ClipRect:= canal.series_clipRect;
end;

destructor TSerieXY.destroy;
begin
  escalaX.Free;
  escalaY.Free;
  inherited destroy;
end;

end.

