!___________________________________________________________________________________ 
!This module provides some useful high-level 2D and 3D Fortran 90 plotting routines 
!based on the DISLIN package. 
!written by Aleksandar Donev (donev@pa.msu.edu), last modifed 11/2/2000 
!NOTE: This file is not heavily commented. For more information, contact me. 
!Feel free to use all or any part of this program. 
!___________________________________________________________________________________ 

MODULE SimpleGraphics
 USE DISLIN
 IMPLICIT NONE

 PUBLIC :: InitGraphics,AddLegend,Plot2D,Plot3D,SurfPlot,EndGraphics
 PRIVATE

 CONTAINS
 !___________________________________________________________________________________
 ! This subroutine must be called before plotting begins
 ! The function my_func can perform any additional level 1 routines needed 
 !___________________________________________________________________________________
 SUBROUTINE InitGraphics(file,file_type,plot_title,x_label,y_label,z_label,my_func)
   CHARACTER(*), OPTIONAL, INTENT(IN) :: file,file_type
   CHARACTER(*), OPTIONAL, INTENT(IN) :: x_label,y_label,z_label
   CHARACTER(*), DIMENSION(:), OPTIONAL, INTENT(IN) :: plot_title
   INTEGER, EXTERNAL, OPTIONAL :: my_func
   
   INTEGER :: my_result,i
 
   IF(PRESENT(file)) CALL SETFIL(file)
   IF(PRESENT(file_type)) CALL METAFL(file_type)
 
   CALL SCRMOD("REVERS")
   IF(PRESENT(my_func)) my_result=my_func(0) ! Level 0 additional routines
   CALL DISINI
   CALL PAGERA
   CALL COMPLX
   CALL NOCHEK
   CALL MIXALF    
   CALL SETMIX('_',"IND")
   CALL SETMIX('^',"EXP")
   CALL SETMIX('$',"RES")
   CALL SETMIX('/',"LEG")    
    
   IF(PRESENT(x_label)) CALL NAME(x_label,'X') 
   IF(PRESENT(y_label)) CALL NAME(y_label,'Y') 
   IF(PRESENT(z_label)) CALL NAME(z_label,'Z')
   IF(PRESENT(plot_title)) THEN
     DO i=1,SIZE(plot_title) 
       CALL TITLIN(plot_title(i),i) 
     END DO 
   END IF     
   IF(PRESENT(my_func)) my_result=my_func(1) ! Level 1 additional routines 
 END SUBROUTINE InitGraphics 
 
 !___________________________________________________________________________________  
 ! This subroutine will add a legend to the plot at the given position (see LEGEND) 
 ! The legend should be stored in an array of strings of the right size 
 ! It should be called AFTER a plotting routine 
 !___________________________________________________________________________________ 
  SUBROUTINE AddLegend(legends,position) 
   CHARACTER(*), DIMENSION(:), INTENT(IN) :: legends 
   INTEGER, OPTIONAL, INTENT(IN) :: position 
   CHARACTER(10*LEN(legends(1))) :: Leg ! For some reason SIZE does not work  
   INTEGER :: i,n,length,leg_pos 
   n=SIZE(legends)
   length=LEN(legends(1)) 
      
   CALL LEGINI(Leg,n,length) 
   CALL FRAME(0) 
   CALL MIXLEG 
   CALL LEGCLR 
   CALL LEGTIT("") 
   DO i=1,n      
     CALL LEGLIN(Leg,legends(i),i) 
   END DO   
      
   IF(PRESENT(position)) THEN  
     leg_pos=position 
   ELSE 
     leg_pos=3 
   END IF   
   CALL LEGEND(Leg,leg_pos)
   
 END SUBROUTINE AddLegend             

 !___________________________________________________________________________________ 
 ! This subroutine plots 2D curves on a graph 
 ! with specified line-types, symbol-types and colors. 
 ! ALL arguments are optional
 ! This time the x and y data are both stored in rank-one arrays-vectors!  
 ! The logical indicator new indicates whether this is a new plot or an old one.
 ! Again, my_func can perform any additional level 2 routines (after GRAF) needed. 
 ! If specified, axis determines the scaling. 
 ! Otherwise, the scaling is chosen so as to fit the data range of x and y.
 !___________________________________________________________________________________    
 SUBROUTINE Plot2D(x,y,plot_spec,new_plot,my_func,axis) 
 
   REAL, DIMENSION(:), INTENT(IN), OPTIONAL :: x 
   REAL, DIMENSION(SIZE(x)), INTENT(IN), OPTIONAL :: y 
   CHARACTER(LEN=3), OPTIONAL, INTENT(IN) :: plot_spec 
   LOGICAL, OPTIONAL, INTENT(IN) :: new_plot
   INTEGER, EXTERNAL, OPTIONAL :: my_func 
   REAL, DIMENSION(4), OPTIONAL, INTENT(IN) :: axis 
 
   INTEGER :: i,my_result,sym_type 
   REAL :: min_x,max_x,min_y,max_y,len_x,len_y 
   CHARACTER(LEN=1) :: type_spec,line_spec,color_spec  
   LOGICAL :: plot_new
   
   IF(PRESENT(new_plot)) THEN
     plot_new=new_plot
   ELSE
     plot_new=.TRUE.
   END IF  

   IF(plot_new) THEN   
     ! Find the scaling from the maximum range of values in the data 
     ! or simply use axis if present: 
     IF(.NOT.PRESENT(axis)) THEN 
       IF(PRESENT(x).AND.PRESENT(y)) THEN
		       min_x=MINVAL(x) 
		       max_x=MAXVAL(x) 
		       min_y=MINVAL(y) 
		       max_y=MAXVAL(y) 
		     ELSE
		       min_x=0.0
		       min_y=0.0
		       max_x=1.0
		       max_y=1.0
		     END IF    
     ELSE 
       min_x=axis(1) 
       max_x=axis(2) 
       min_y=axis(3) 
       max_y=axis(4) 
     END IF 
     len_x=max_x-min_x 
     len_y=max_y-min_y 
     CALL GRAF(min_x-len_x/20,max_x+len_x/20,min_x,len_x/10,min_y-len_y/20,max_y+len_y/20,min_y,len_y/10) 
     CALL TITLE
   END IF
      
   IF(PRESENT(my_func)) my_result=my_func(2) 
 
   ! This big chunk sets the appropriate symbol or line type and color 
   ! and then plots the corresponding curve.
   ! We don't change the settings if this is not a new plot and there is no specification
   ! Therefore, if this is not a new plot and there is no specification, DISLIN defaults 
   ! will be used, not mine. So be wise!
   IF(plot_new. OR. (.NOT.plot_new.AND.PRESENT(plot_spec))) THEN
				   IF(PRESENT(plot_spec)) THEN
				     type_spec=plot_spec(1:1)
				     line_spec=plot_spec(2:2)
				     color_spec=plot_spec(3:3)
				   ELSE
				     type_spec='S'
				     line_spec='C'
				     color_spec='B'
				   END IF       
       SELECT CASE(type_spec) 
         CASE('S') 
           CALL INCMRK(-1) 
           SELECT CASE(line_spec) 
             CASE('S') 
               sym_type=0 
             CASE('C') 
               sym_type=1 
             CASE('T') 
               sym_type=2 
             CASE('D') 
               sym_type=5 
             CASE('s') 
               sym_type=16 
             CASE('c') 
               sym_type=21 
             CASE('t') 
               sym_type=20 
             CASE('d') 
               sym_type=19 
             CASE DEFAULT 
               sym_type=1 
           END SELECT 
         CALL MARKER(sym_type) 
         CASE('L') 
           CALL INCMRK(0) 
           SELECT CASE(line_spec) 
             CASE('-') 
               CALL SOLID 
             CASE('.') 
               CALL DOTL 
             CASE(':') 
               CALL DASHL 
             CASE('|') 
               CALL CHNDOT 
             CASE DEFAULT 
               CALL DOTL 
           END SELECT 
         CASE DEFAULT 
           CALL INCMRK(1) 
           CALL MARKER(1) 
           CALL DOTL 
       END SELECT 
       CALL SETVLT("SMALL") 
       SELECT CASE(color_spec) 
         CASE('R') 
           CALL SETCLR(2) 
         CASE('B') 
           CALL SETCLR(4) 
         CASE('G') 
           CALL SETCLR(3) 
         CASE('Y') 
           CALL SETCLR(5) 
         CASE DEFAULT 
           CALL SETCLR(2) 
       END SELECT 
   END IF

   IF(PRESENT(x).AND.PRESENT(y)) CALL CURVE(x,y,SIZE(x))
 
 END SUBROUTINE Plot2D  

  SUBROUTINE Plot3D(x,y,z,plot_spec,new_plot,my_func,axis,view) 
 
   REAL, DIMENSION(:), INTENT(IN), OPTIONAL :: x 
   REAL, DIMENSION(SIZE(x)), INTENT(IN), OPTIONAL :: y,z 
   CHARACTER(LEN=3), OPTIONAL, INTENT(IN) :: plot_spec 
   LOGICAL, OPTIONAL, INTENT(IN) :: new_plot
   INTEGER, EXTERNAL, OPTIONAL :: my_func 
   REAL, DIMENSION(6), OPTIONAL, INTENT(IN) :: axis 
   REAL, DIMENSION(3), OPTIONAL :: view   
 
   INTEGER :: i,my_result,sym_type 
   REAL :: min_x,max_x,min_y,max_y,min_z,max_z,len_x,len_y,len_z
   CHARACTER(LEN=1) :: type_spec,line_spec,color_spec  
   LOGICAL :: plot_new
   
   IF(PRESENT(new_plot)) THEN
     plot_new=new_plot
   ELSE
     plot_new=.TRUE.
   END IF  

   IF(plot_new) THEN   
     ! Find the scaling from the maximum range of values in the data 
     ! or simply use axis if present: 
     IF(.NOT.PRESENT(axis)) THEN 
       IF(PRESENT(x).AND.PRESENT(y)) THEN
		       min_x=MINVAL(x) 
		       max_x=MAXVAL(x) 
		       min_y=MINVAL(y) 
		       max_y=MAXVAL(y)
		       min_z=MINVAL(z) 
		       max_z=MAXVAL(z)		        
		     ELSE
		       min_x=0.0
		       min_y=0.0
		       min_z=1.0
		       max_x=1.0
		       max_y=1.0
		       max_z=1.0
		     END IF    
     ELSE 
       min_x=axis(1) 
       max_x=axis(2) 
       min_y=axis(3) 
       max_y=axis(4) 
       min_z=axis(5) 
       max_z=axis(6)       
     END IF 
     len_x=max_x-min_x 
     len_y=max_y-min_y
     len_z=max_z-min_z 
    IF(PRESENT(view)) CALL VIEW3D(view(1),view(2),view(3),"ANGLE")
    CALL GRAF3D(min_x-len_x/20,max_x+len_x/20,min_x,len_x/10,min_y-len_y/20,max_y+len_y/20,min_y,len_y/10, &
       min_z-len_z/20,max_z+len_z/20,min_z,len_z/10)
     CALL TITLE
   END IF
      
   IF(PRESENT(my_func)) my_result=my_func(2) 
 
   ! This big chunk sets the appropriate symbol or line type and color 
   ! and then plots the corresponding curve.
   ! We don't change the settings if this is not a new plot and there is no specification
   ! Therefore, if this is not a new plot and there is no specification, DISLIN defaults 
   ! will be used, not mine. So be wise!
   IF(plot_new. OR. (.NOT.plot_new.AND.PRESENT(plot_spec))) THEN
				   IF(PRESENT(plot_spec)) THEN
				     type_spec=plot_spec(1:1)
				     line_spec=plot_spec(2:2)
				     color_spec=plot_spec(3:3)
				   ELSE
				     type_spec='S'
				     line_spec='C'
				     color_spec='B'
				   END IF       
       SELECT CASE(type_spec) 
         CASE('S') 
           CALL INCMRK(-1) 
           SELECT CASE(line_spec) 
             CASE('S') 
               sym_type=0 
             CASE('C') 
               sym_type=1 
             CASE('T') 
               sym_type=2 
             CASE('D') 
               sym_type=5 
             CASE('s') 
               sym_type=16 
             CASE('c') 
               sym_type=21 
             CASE('t') 
               sym_type=20 
             CASE('d') 
               sym_type=19 
             CASE DEFAULT 
               sym_type=1 
           END SELECT 
         CALL MARKER(sym_type) 
         CASE('L') 
           CALL INCMRK(0) 
           SELECT CASE(line_spec) 
             CASE('-') 
               CALL SOLID 
             CASE('.') 
               CALL DOTL 
             CASE(':') 
               CALL DASHL 
             CASE('|') 
               CALL CHNDOT 
             CASE DEFAULT 
               CALL DOTL 
           END SELECT 
         CASE DEFAULT 
           CALL INCMRK(1) 
           CALL MARKER(1) 
           CALL DOTL 
       END SELECT 
       CALL SETVLT("SMALL") 
       SELECT CASE(color_spec) 
         CASE('R') 
           CALL SETCLR(2) 
         CASE('B') 
           CALL SETCLR(4) 
         CASE('G') 
           CALL SETCLR(3) 
         CASE('Y') 
           CALL SETCLR(5) 
         CASE DEFAULT 
           CALL SETCLR(2) 
       END SELECT 
   END IF

   IF(PRESENT(x).AND.PRESENT(y).AND.PRESENT(z)) CALL CURV3D(x,y,z,SIZE(x))
 
 END SUBROUTINE Plot3D 
   
 !___________________________________________________________________________________   
 ! This subroutine plots a 3D or 2D surface (color) plot from given data 
 ! The function should not be called more than once--this crowds the plots
 ! and is rarely useful, so it is disabled.
 ! The data is given on a regular grid in x and y (array vectors)
 ! in the form of a rank-2 matrix z=f(x,y).
 ! my_func can perform any additional level 3 routines (after GRAF3) needed. 
 ! The color_range for the color pallette can be given explicitly.
 ! If specified, axis determines the scaling. 
 ! Otherwise, the scaling is chosen so as to fit the data range of x and y. 
 ! The view point (camera) can also be given in the form of (theta,phi,r).
 !___________________________________________________________________________________     
 SUBROUTINE SurfPlot(x,y,z,plot_spec,my_func,color_range,axis,view,contours) 
   REAL, DIMENSION(:), INTENT(IN) :: x,y 
   REAL, DIMENSION(SIZE(X),SIZE(Y)), INTENT(IN) :: z 
   CHARACTER(3), OPTIONAL, INTENT(IN) :: plot_spec 
   INTEGER, EXTERNAL, OPTIONAL :: my_func 
   REAL, DIMENSION(2), OPTIONAL, INTENT(IN) :: color_range 
   REAL, DIMENSION(6), OPTIONAL, INTENT(IN) :: axis 
   REAL, DIMENSION(3), OPTIONAL, INTENT(IN) :: view 
   REAL, DIMENSION(:), OPTIONAL, INTENT(IN) :: contours
 
   INTEGER :: my_result, this_color,n_labels 
   INTEGER, DIMENSION(:), ALLOCATABLE :: these_labels
   REAL :: min_x,max_x,min_y,max_y,min_z,max_z,len_x,len_y,len_z
   REAL :: this_label, this_range(2) 
   INTEGER :: i,sym_type 
   CHARACTER(3) :: this_spec="3RS" 
    
   ! Again we determine the range of the axis:  
   IF(.NOT.PRESENT(axis)) THEN 
     min_x=MINVAL(x) 
     max_x=MAXVAL(x) 
     min_y=MINVAL(y) 
     max_y=MAXVAL(y) 
     min_z=MINVAL(z) 
     max_z=MAXVAL(z) 
   ELSE 
     min_x=axis(1) 
     max_x=axis(2) 
     min_y=axis(3) 
     max_y=axis(4) 
     min_z=axis(5) 
     max_z=axis(6) 
   END IF 
   len_x=max_x-min_x 
   len_y=max_y-min_y 
   len_z=max_z-min_z 
 
   IF(PRESENT(view)) CALL VIEW3D(view(1),view(2),view(3),"ANGLE") 
   IF(PRESENT(color_range)) THEN
     CALL ZSCALE(color_range(1),color_range(2)) 
     this_range=color_range
   ELSE  
     this_range=(/min_z,max_z/)
   END IF   
   IF(PRESENT(plot_spec)) this_spec=plot_spec 
   IF(PRESENT(contours)) THEN
     n_labels=SIZE(contours)
     ALLOCATE(these_labels(n_labels))
     these_labels=contours
   ELSE
     n_labels=1
     ALLOCATE(these_labels(n_labels))   
     these_labels=(min_z+max_z)/2
   END IF
       
   ! We set the appropriate color pallete: 
   SELECT CASE(this_spec(3:3)) 
    CASE('V') 
       CALL SETVLT("VGA") 
     CASE('G') 
       CALL SETVLT("GREY") 
     CASE('g') 
       CALL SETVLT("RGREY") 
     CASE('r') 
       CALL SETVLT("RRAIN") 
     CASE DEFAULT 
       CALL SETVLT("RAIN") 
   END SELECT 
   SELECT CASE(this_spec(1:1)) 
     ! This part plots a 2D color plot: 
     ! It is highly improvized and WRONG when 
     ! an axis is supplied and they are different from the minmax range of xy
     ! This is because CRVMAT does not accept x and y data:
     CASE('M') 
       ! In this case the second character determines the resolution increase: 
       i=IACHAR(this_spec(2:2))-48 
       IF(.NOT.((i>=1).AND.(i<=9))) i=1 
       CALL AUTRES(SIZE(x),SIZE(y))      
       CALL GRAF3(min_x,max_x,min_x,len_x/10,min_y,max_y,min_y,len_y/10, & 
         min_z-len_z/20,max_z+len_z/20,min_z,len_z/10) 
       CALL TITLE 
       IF(PRESENT(my_func)) my_result=my_func(3) 
       CALL CRVMAT(z,SIZE(x),SIZE(y),i,i)  ! THIS IS WRONG!--use for demos only
     CASE('C')
       SELECT CASE(this_spec(2:2)) 
         CASE('N')
           CALL LABELS("NONE","CONTUR")    
         CASE DEFAULT
           CALL LABELS("FLOAT","CONTUR")     
       END SELECT   
       CALL GRAF3(min_x,max_x,min_x,len_x/10,min_y,max_y,min_y,len_y/10, & 
         this_range(1),this_range(2),this_range(1),(this_range(2)-this_range(1))/10)       
       ! CALL GRAF(min_x-len_x/20,max_x+len_x/20,min_x,len_x/10,min_y-len_y/20,max_y+len_y/20,min_y,len_y/10)
       CALL TITLE 
       IF(PRESENT(my_func)) my_result=my_func(2) 
       DO i=1,n_labels
         this_label=contours(i)
         this_color=MODULO(INT(256*(this_label-this_range(1))/(this_range(2)-this_range(1))),256)
         CALL SETCLR(this_color)
         CALL CONTUR(x,SIZE(x),y,SIZE(y),z,this_label) 
       END DO                        
     ! This part plots a 3D surface plot with the given specifications:         
     CASE DEFAULT 
       CALL GRAF3D(min_x-len_x/20,max_x+len_x/20,min_x,len_x/10,min_y-len_y/20,max_y+len_y/20,min_y,len_y/10, & 
         min_z-len_z/20,max_z+len_z/20,min_z,len_z/10) 
       CALL TITLE 
       IF(PRESENT(my_func)) my_result=my_func(3) 
       SELECT CASE(this_spec(2:2)) 
         CASE('M') 
           CALL SURFCE(x,SIZE(x),y,SIZE(y),z) 
         CASE('C') 
           CALL SHDMOD("SMOOTH","SURFACE") 
           CALL SURSHD(x,SIZE(x),y,SIZE(y),z) 
         CASE DEFAULT 
           CALL SHDMOD("SMOOTH","SURFACE") 
           CALL SURSHD(x,SIZE(x),y,SIZE(y),z) 
           CALL SURFCE(x,SIZE(x),y,SIZE(y),z) 
       END SELECT 
   END SELECT 
   DEALLOCATE(these_labels)

 END SUBROUTINE SurfPlot 
 
 !___________________________________________________________________________________   
 ! This function needs to be called to end the plotting 
 ! my_func now specifies the level 0 routines at the end 
 !___________________________________________________________________________________     
 SUBROUTINE EndGraphics(my_func) 
   INTEGER, EXTERNAL, OPTIONAL :: my_func 
   INTEGER :: my_result 
   IF(PRESENT(my_func)) my_result=my_func(-1) 
   CALL DISFIN 
 END SUBROUTINE EndGraphics 
 
ENDMODULE SimpleGraphics 

