Perl :: Aufgabe #9

2 Lösungen Lösungen öffentlich

CSV-Datei auslesen und aus den Daten eine Tabelle und ein Diagramm erzeugen (CGI)

Fortgeschrittener - Perl von Gustl - 16.08.2012 um 10:36 Uhr
Erstellen Sie ein Perl- Skript, das die angehängte CSV- Datei (EUR/USD Dollarkurs von einem Jahr) in ein Array einliest. Diese eingelesenen Werte sollen in einer Tabelle dargestellt werden und sich in einem Linien-, Punkt- oder Balkendiagramm widerspiegeln.

In dem Diagramm soll nur jeder vierte Wert ausgegeben werden.
Das Datum soll in folgendes Format konvertiert werden: DD.MM.YY (16.08.12)


Lösungen:

vote_ok
von progdoc (2220 Punkte) - 20.08.2012 um 22:25 Uhr
Quellcode ausblenden Perl-Code
#!/usr/bin/perl 
use strict ;
use warnings ;
use GD::Graph::lines ;
#I first downloaded the data file
open ( FH , "<" , "dataset.txt" )
   or die "Can't open the data file!\n" ;
my @dataset ;
while ( <FH> ) {
   chomp ;
   next unless $_ =~ /\d+/ ; #don't read in the column headers
   my @data = split ( ";" , $_ ) ;
   for my $i ( 0..$#data ) {
      push ( @{$dataset[ $i ]} , $data[ $i ] ) ;
   }
}
close( FH ) ;
#we now want to reverse the data sets to reflect the chronological order
for my $i ( 0..$#dataset ) {
   my @reversed = reverse @{$dataset[ $i ]} ;
   @{$dataset[ $i ]} = @reversed ;
}
#convert the data values into the desired format
@{$dataset[ 0 ] } =  map { my ( $year , $month , $day ) = split ( /-/ , $_ ) ;
   join ( "." , $day , $month , substr ( $year , -2 ) ) } @{$dataset[ 0 ] } ;
#convert the data into numbers with a decimal point
for my $i ( 1..4 ) {
   map { $_ =~ s/,/\./ } @{$dataset[ $i ] } ;
}
#for the y axis numbering, find the smallest and the greatest data value
my $datapointnumber = @{$dataset[ 0 ] } ;
my $graph = GD::Graph::lines->new( 1600 , 400 ) ;
$graph->set( 
      x_label            => "Datum" ,
      y_label            => "Kurse( Start , höchster, tiefster, Schluss )" ,
      title              => "Kursentwicklung des Euro zum Dollar von 2011 bis 2012" ,
      y_tick_number      => 5 ,
      y_number_format    => \&y_format ,
      x_label_skip       => 4 ,
      x_labels_vertical  => 1 ,
      transparent        => 0 , 
) ;
$graph->set_legend( "Startkurs" , "Hoechstkurs" , "Tiefstkurs" , "Abschlusskurs" ) ;
my $gd = $graph->plot( \@dataset ) or die $graph->error ;
open ( IMG , '>exchange.png' ) or die $! ;
binmode IMG ;
print IMG $gd->png ;
close IMG ;

sub y_format {
   my $value = shift ;
   $value =~ s/\./,/ ;
   return  $value . "0" . " \$" ;
}
vote_ok
von Gustl (6560 Punkte) - 16.09.2012 um 19:19 Uhr
Meine Lösung ist vielleicht nicht ganz ausgereift, aber es kann zumindest ein dynamisches Diagramm erstellt werden, welches mit canvas/javascript dargestellt wird. Beim Datum habe ich das Jahr noch abgeschnitten.

Quellcode ausblenden Perl-Code
#!/usr/bin/perl

use strict;
use POSIX; 

my ($one_ref_array, $two_ref_array) = read_csv_file( "eur_usd1.csv");
my @array_table = @$one_ref_array;
my @array_diagramm = @$two_ref_array;
make_html_table( \@array_diagramm, "tabelle.html");
make_html_diagramm( \@array_diagramm, "diagramm.html", 400, 800);

# Liest die csv-datei ein
# 1. Paramter: Pfad von csv-Datei
# 2. Paramter: jeder x Eintrag wird dem Array für das Diagramm hinzugefügt
sub read_csv_file{
  my $csv_file     = $_[0];   
  
  my (@arr_table, @arr_diagramm);
  my @head_table;
  my $i = 0;
  
  # csv-Datei öffnen
  open(CSV,$csv_file) or die "Die Datei $csv_file konnte nicht geoeffnet werden.";
  while(<CSV>){  
  
    # Spaltennamen auslesen    
    if($i == 0){
      chomp($_);
      @head_table = split(/;/,$_);
    }
    
    # Inhalt auslesen und in table-array und diagramm-array hinzufügen
    else{
      chomp($_);
      my @line_array = split(/;/,$_);
      my $hash_ref;
      for(my $x = 0; $x < scalar(@line_array); $x++){
        if($head_table[$x] eq "Datum"){
          $hash_ref->{$head_table[$x]} = convert_datum($line_array[$x]);
        }
        else{
          $hash_ref->{$head_table[$x]} = $line_array[$x];
          $hash_ref->{$head_table[$x]} =~ tr/,/\./;
        }    
      }  
      push( @arr_table, $hash_ref );
      push( @arr_diagramm, $hash_ref ); 
    }   
    $i++;
  }    
  close CSV;
  
  return (\@arr_table, \@arr_diagramm);
}

sub make_html_table{
  my @arr_diagramm = @{$_[0]};
  my $file = $_[1]; 
  
  open(TABLE_HTML,">".$file);
  print TABLE_HTML "
  <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 
    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
  <html xmlns='http://www.w3.org/1999/xhtml'>
  <head>
  <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
      <link rel='stylesheet' href='style.css' type='text/css' />             
  </head>
  <body>";
  print TABLE_HTML "<table cellspacing='1' cellpadding='0'><tr>";
  
  my $hashref = $arr_diagramm[0]; 
  for my $key_th (keys %$hashref)
  {
        print TABLE_HTML "<th>".$key_th."</th>";
  }  
  print TABLE_HTML "</tr>";
  
  foreach (@arr_diagramm){
     print TABLE_HTML "<tr>";
     for my $key (keys %$_)
     {
            print TABLE_HTML "<td>".$_->{$key}."</td>";            
     }
     print TABLE_HTML "</tr>";
  }  
  print TABLE_HTML "</table></body></html>"; 
     
  close TABLE_HTML;
}

sub convert_datum{
  my $datum = shift;  
  my @date_array = split(/-/,$datum);
  my $year = substr($date_array[0],2,2);
  return $date_array[2].".".$date_array[1].".".$year;
}

sub make_html_diagramm{
  my @arr_diagramm = @{$_[0]};
  my $file = $_[1];
  my $hoehe = $_[2];
  my $width = $_[3];
  
  my $x_hoehe_bedarf_top = 50;
  my $y_breite_bedarf_left = 20;
  
  my $x_hoehe_bedarf = 40;
  my $y_breite_bedarf = 70; 
  
  my $hoehe_diagramm = $hoehe - $x_hoehe_bedarf - $x_hoehe_bedarf_top;
  my $width_diagramm = $width - $y_breite_bedarf - $y_breite_bedarf_left; 
  
  # max und min werte ermitteln
  my $max_wert = get_max(\@arr_diagramm);
  my $min_wert = get_min(\@arr_diagramm);
  
  # differnez von max und min werte
  my $diff = floor(($max_wert - $min_wert) * 1000);
  # y schritt (zb. schritt = 6, pro 6px +1 wert)
  my $schritt = $hoehe_diagramm / $diff;
  # x schritt
  my $schritt_left = $width_diagramm / scalar(@arr_diagramm);
   
  my $left = 0 - $y_breite_bedarf_left;
  foreach(@arr_diagramm){
    $left += $schritt_left; 
    $_->{x} = $left;
    print "\nleft: ".$left;
    $_->{y} = ($_->{Schlusskurs} - $min_wert) * 1000 * $schritt; 
    if($_->{Datum} =~ /(.{6})/){
      $_->{Datum} = $1;
    }  
  }   
  
  # Beschriftungen und Hintergrundlinien
  # y-achse
  my $y_desc_abstand = $hoehe_diagramm / 25;
  for(my $i = $hoehe_diagramm; $i >= 1; $i-- ){
    if( ($hoehe_diagramm / $i) >= $y_desc_abstand){
      $y_desc_abstand = $i;
      last;  
    }
  }
  my $y_desc_anzahl = $hoehe_diagramm/$y_desc_abstand;
  my $step_y_desc = $diff/$y_desc_anzahl;
  $y_desc_anzahl = floor($y_desc_anzahl);
  my @y_desc;
  foreach(0..($y_desc_anzahl)){
    my $hashref;
    $hashref->{desc} = int( (($min_wert*1000) + ($step_y_desc * $_)))/1000;
    
    $hashref->{y}    = $hoehe_diagramm + $x_hoehe_bedarf_top - ($y_desc_abstand * $_); 
    print $hashref->{y}." ".$hashref->{desc}."\n"; 
    push(@y_desc, $hashref);
  }
  # x-achse
  my $x_teiler = ($width - $y_breite_bedarf_left) / scalar(@arr_diagramm);
  print "\nteiler vor: ".$x_teiler;
  for(my $i = 1; $i >= 1; $i *= 2){
    if($x_teiler >= (50/$i) ){
      $x_teiler = $i;
      last; 
    }
  }
  print "\nteiler nach: ".$x_teiler;  
  print "\nMin: ".$min_wert;
  print "\nMax: ".$max_wert;
  print "\nSchritt: ".$schritt;
  print "\nDiff: ".$diff;
  print "\ndesc_anzahl: ".$y_desc_anzahl;
  print "\ndesc_abstand: ".$y_desc_abstand;
  print "\nstep_y_desc: ".$step_y_desc;
  print "\nscalar: ".scalar(@arr_diagramm);
  print "\nhoehe_diagramm: ".$hoehe_diagramm; 
  
  open(DIAGRAMM_HTML,">".$file);
  
  print DIAGRAMM_HTML "<!doctype html>
<html lang='de' xml:lang='de'>
  <head>
  <meta charset='utf-8'>
    <link rel='stylesheet' href='style.css' type='text/css' />             
  </head>
  <body>
  <canvas id='canvasLinie' width='".($width)."' height='".($hoehe)."' style='border: 1px solid silver;'>
      ein Browser unterstützt das Canvas-Element nicht.
  </canvas>
  <script type='text/javascript'>
      var canvas = document.getElementById('canvasLinie');
      var context = canvas.getContext('2d'); 
      context.lineWidth = 0.4;";
  for(my $i = 0; $i < scalar(@arr_diagramm); $i++ ){
    if($i == 0){
      print DIAGRAMM_HTML "context.moveTo(".(($width-$y_breite_bedarf)-$arr_diagramm[$i]->{x}- $y_breite_bedarf_left).", ".(($hoehe-$x_hoehe_bedarf)-$arr_diagramm[$i]->{y}).");";
    }
    else{
      print DIAGRAMM_HTML "context.lineTo(".(($width-$y_breite_bedarf)-$arr_diagramm[$i]->{x}- $y_breite_bedarf_left).", ".(($hoehe-$x_hoehe_bedarf)-$arr_diagramm[$i]->{y}).");";
     }
  }  
  print DIAGRAMM_HTML "context.stroke();";
   
   
  print DIAGRAMM_HTML "var y_lines = canvas.getContext('2d');";
  print DIAGRAMM_HTML "y_lines.lineWidth = 0.2;"; 
  foreach(@y_desc){
    print DIAGRAMM_HTML "y_lines.fillText('".$_->{desc}."',".($width-60+5).",".($_->{y}+2).");\n";      
    print DIAGRAMM_HTML "y_lines.moveTo(".$y_breite_bedarf_left.",".($_->{y}).");
                         context.lineTo(".($width-60).",".($_->{y}).");\n";
  }           
  print DIAGRAMM_HTML "y_lines.stroke();";
  
  print DIAGRAMM_HTML "var x_lines = canvas.getContext('2d');";
  print DIAGRAMM_HTML "x_lines.lineWidth = 0.2;";
  for(my $i = 0; $i < scalar(@arr_diagramm); $i += $x_teiler ){ 
    print DIAGRAMM_HTML "x_lines.fillText('".$arr_diagramm[$i]->{Datum}."',".(($width-$y_breite_bedarf)- $y_breite_bedarf_left-$arr_diagramm[$i]->{x}-13).",".($hoehe-$x_hoehe_bedarf+24).");\n"; 
    print DIAGRAMM_HTML "x_lines.moveTo(".(($width-$y_breite_bedarf)- $y_breite_bedarf_left-$arr_diagramm[$i]->{x}).",".$x_hoehe_bedarf_top.");context.lineTo(".(($width-$y_breite_bedarf)- $y_breite_bedarf_left-$arr_diagramm[$i]->{x}).",".($hoehe-$x_hoehe_bedarf+10).");\n";    
  }
  print DIAGRAMM_HTML "x_lines.stroke();";  
    
  print DIAGRAMM_HTML "var head = canvas.getContext('2d'); 
 head.font=\"18px Verdana\";
 head.fillText(\"Euro / US Dollar - Kurs (EUR/USD)\", 30, 33);</script>  
  </body>
  </html>";
  close DIAGRAMM_HTML;
}

sub get_max{
  my @arr_diagramm = @{$_[0]};
  my $max = 0;  
  foreach(@arr_diagramm) {
    $max = $_->{Schlusskurs} if ($_->{Schlusskurs} > $max && $_->{Schlusskurs} > 0 );
  }
  return $max;
}

sub get_min{
  my @arr_diagramm = @{$_[0]};
  my $min = 10000;  
  foreach(@arr_diagramm) { 
    $min = $_->{Schlusskurs} if ($_->{Schlusskurs} < $min && $_->{Schlusskurs} > 0 );    
  } 
  return $min;
}


Hier ein Link zur Ausgabe (Diagramm + Tabelle)