Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Esecuzione di test

Generazione di grafici con WPF

James McCaffrey

Scaricare il codice di esempio

James McCaffreyGenerazione di un grafico da un insieme di dati relativi a test è una comune attività di sviluppo del software. In base alla mia esperienza, l'approccio più comune è per importare dati in un foglio di calcolo di Excel, quindi produrre funzionalità grafico manualmente utilizzando Excel incorporata grafici. Ciò funziona bene nella maggior parte delle situazioni, ma se i dati sottostanti vengono modificati frequentemente, creare grafici a mano può risultare noiosa. Nell'articolo di mese questo verrà illustrato come automatizzare il processo tramite la tecnologia Windows Presentation Foundation (WPF). Per vedere dove sta intestazione, esaminare di Figura 1. Il grafico mostra un conteggio di aprire Data e bug chiusi e generato in tempo reale con un breve programma WPF che legge i dati da un file di testo semplice.

Figure 1 Programmatically Generated Bug-Count Graph
Figura 1 di conteggio di bug generati a livello grafico

Bug aperto, rappresentati da cerchi rossi sulla riga blu aumentare rapidamente accanto all'inizio dell'attività di sviluppo, quindi ricerca nel tempo, ovvero informazioni che potrebbero essere utili per la stima Data zero bug bounce. Migliorare costantemente i bug chiusi (Indicatori triangolari sulla linea verde).

Tuttavia, le informazioni potrebbero essere utili nella produzione spesso sono limitate risorse di ambienti di sviluppo e generazione manualmente tale grafico potrebbero non essere pena. Ma utilizza la tecnica che spiegherò, creazione di grafici come questo è semplice e veloce.

Nelle sezioni seguenti verrà presentare e descrivere in dettaglio il codice C# generato il grafico in di Figura 1. Questo articolo si presuppone conoscenza intermedia a livello di codice C# e una base familiarità con WPF. Ma anche se si ha familiarità con entrambi, ritengo che potrai seguire la discussione senza troppa difficultly. Si è sicuro che troverete la tecnica un'aggiunta utile e interessante per i set di competenza.

Impostazione del progetto

Ho iniziato l'avvio di Visual Studio 2008 e creando un nuovo progetto C# utilizzando il modello di applicazione WPF. La libreria .NET Framework 3.5 selezionato dal controllo elenco a discesa nell'area superiore destra della finestra di dialogo Nuovo progetto. Denominato progetto BugGraph. Anche se a livello di programmazione è possibile generare grafici utilizzando primitive WPF, ho utilizzato la libreria DynamicDataDisplay pratica sviluppata da Microsoft Research laboratorio.

È possibile scaricare gratuitamente la libreria da CodePlex open source che ospita il sito all'indirizzo codeplex.com/dynamicdatadisplay . Salvata la copia nella directory principale del progetto BugGraph, quindi aggiungere un riferimento alla DLL nel progetto facendo doppio clic sul nome del progetto, selezionare l'opzione Aggiungi riferimento e che punta al file DLL nella directory principale.

Successivamente, è stata creata l'origine dati. In un ambiente di produzione, è possono individuare i dati in un foglio di calcolo di Excel, un database SQL o un file XML. Per semplicità, ho utilizzato un file di testo semplice. Nella finestra Esplora soluzioni di Visual Studio è selezionata sul nome del progetto e selezionare Aggiungi | nuovo elemento dal menu di scelta rapida. Quindi è stato scelto il file di testo rinominato il file BugInfo.txt articolo e si fa clic sul pulsante Aggiungi. Ecco i dati fittizi:

01/15/2010:0:0 02/15/2010:12:5 03/15/2010:60:10 04/15/2010:88:20 05/15/2010:75:50 06/15/2010:50:70 07/15/2010:40:85 08/15/2010:25:95 09/15/2010:18:98 10/15/2010:10:99

Il primo campo delimitato da due punti in ogni riga contiene una data, la seconda contiene il numero di bug aperti nella data associata e il terzo campo viene visualizzato il numero di bug chiusi. Come vedrà a breve, la libreria DynamicDataDisplay consente di gestire la maggior parte dei tipi di dati.

Successivamente fatto doppio clic sul file Window1.XAML per caricare le definizioni di interfaccia utente per il progetto. Ho aggiunto un riferimento alla libreria di grafico DLL e leggermente modificato predefinito altezza, larghezza e gli attributi di sfondo di WPF di visualizzazione area, come segue:

xmlns:d3="https://research.microsoft.com/DynamicDataDisplay/1.0" 
Title="Window1" WindowState="Normal" Height="500" Width="800" Background="Wheat">

In seguito, è stato aggiunto l'oggetto tracciato chiave, illustrato in di Figura 2.

Figura 2 Aggiunta chiave disegno oggetto

<d3:ChartPlotter Name="plotter" Margin="10,10,20,10">
  <d3:ChartPlotter.HorizontalAxis>
    <d3:HorizontalDateTimeAxis Name="dateAxis"/>
  </d3:ChartPlotter.HorizontalAxis>
  <d3:ChartPlotter.VerticalAxis>
    <d3:VerticalIntegerAxis Name="countAxis"/>
  </d3:ChartPlotter.VerticalAxis>

  <d3:Header FontFamily="Arial" Content="Bug Information"/>
  <d3:VerticalAxisTitle FontFamily="Arial" Content="Count"/>
  <d3:HorizontalAxisTitle FontFamily="Arial" Content="Date"/>
</d3:ChartPlotter>

L'elemento ChartPlotter è l'oggetto di visualizzazione principale. Nella definizione della relativa aggiunta dichiarazioni per un asse orizzontale data e un asse verticale integer. Il tipo di asse predefinito per la libreria DynamicDataDisplay è un numero decimale, che è di tipo double in termini di C#, non è necessaria per il tipo è una dichiarazione esplicita dell'asse. Ho inoltre aggiunto una dichiarazione del titolo di intestazione e le dichiarazioni del titolo dell'asse. Figura 3 mio progetto viene illustrato finora.

Figure 3 BugGraph Program Design
Figura 3 BugGraph Program Design

Per l'origine

Una volta che avevo configurato gli aspetti statici del progetto, è stato possibile aggiungere il codice che potrebbe leggere i dati di origine e generare a livello di codice il grafico. Fatto doppio clic su Window1.XAML.cs nella finestra Esplora soluzioni per caricare il file C# nell'editor di codice. Figura 4 Elenca l'intero codice sorgente del programma che ha generato il grafico in di Figura 1.

Figura 4 di codice sorgente per il progetto BugGraph

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media; // Pen

using System.IO;
using Microsoft.Research.DynamicDataDisplay; // Core functionality
using Microsoft.Research.DynamicDataDisplay.DataSources; // EnumerableDataSource
using Microsoft.Research.DynamicDataDisplay.PointMarkers; // CirclePointMarker

namespace BugGraph
{
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
      Loaded += new RoutedEventHandler(Window1_Loaded);
    }

    private void Window1_Loaded(object sender, RoutedEventArgs e)
    {
      List<BugInfo> bugInfoList = LoadBugInfo("..\\..\\BugInfo.txt");

      DateTime[] dates = new DateTime[bugInfoList.Count];
      int[] numberOpen = new int[bugInfoList.Count];
      int[] numberClosed = new int[bugInfoList.Count];

      for (int i = 0; i < bugInfoList.Count; ++i)
      {
        dates[i] = bugInfoList[i].date;
        numberOpen[i] = bugInfoList[i].numberOpen;
        numberClosed[i] = bugInfoList[i].numberClosed;
      }

      var datesDataSource = new EnumerableDataSource<DateTime>(dates);
      datesDataSource.SetXMapping(x => dateAxis.ConvertToDouble(x));

      var numberOpenDataSource = new EnumerableDataSource<int>(numberOpen);
      numberOpenDataSource.SetYMapping(y => y);

      var numberClosedDataSource = new EnumerableDataSource<int>(numberClosed);
      numberClosedDataSource.SetYMapping(y => y);

      CompositeDataSource compositeDataSource1 = new
        CompositeDataSource(datesDataSource, numberOpenDataSource);
      CompositeDataSource compositeDataSource2 = new
        CompositeDataSource(datesDataSource, numberClosedDataSource);

      plotter.AddLineGraph(compositeDataSource1,
        new Pen(Brushes.Blue, 2),
        new CirclePointMarker { Size = 10.0, Fill = Brushes.Red },
        new PenDescription("Number bugs open"));

      plotter.AddLineGraph(compositeDataSource2,
        new Pen(Brushes.Green, 2),
        new TrianglePointMarker { Size = 10.0,
          Pen = new Pen(Brushes.Black, 2.0),
            Fill = Brushes.GreenYellow },
        new PenDescription("Number bugs closed"));

      plotter.Viewport.FitToView();

    } // Window1_Loaded()

    private static List<BugInfo> LoadBugInfo(string fileName)
    {
      var result = new List<BugInfo>();
      FileStream fs = new FileStream(fileName, FileMode.Open);
      StreamReader sr = new StreamReader(fs);
     
      string line = "";
      while ((line = sr.ReadLine()) != null)
      {
        string[] pieces = line.Split(':');
        DateTime d = DateTime.Parse(pieces[0]);
        int numopen = int.Parse(pieces[1]);
        int numclosed = int.Parse(pieces[2]);
        BugInfo bi = new BugInfo(d, numopen, numclosed);
        result.Add(bi);
      }
      sr.Close();
      fs.Close();
      return result;
    }

  } // class Window1

  public class BugInfo {
  public DateTime date;
  public int numberOpen;
  public int numberClosed;

  public BugInfo(DateTime date, int numberOpen, int numberClosed) {
    this.date = date;
    this.numberOpen = numberOpen;
    this.numberClosed = numberClosed;
  }

}} // ns

Dopo aver eliminato l'inutile utilizzando le istruzioni dello spazio dei nomi (ad esempio System.Windows.Shapes), che sono state generate dal modello di Visual Studio. Quindi ho aggiunto utilizzando istruzioni per tre spazi dei nomi della libreria DynamicDataDisplay in modo non sia necessario qualificare in modo completo i nomi. Successivamente, nel costruttore operazione aggiunto un evento per la routine definito dal programma principale:

Loaded += new RoutedEventHandler(Window1_Loaded);

Ecco come ho iniziato la routine main:

private void Window1_Loaded(object sender, RoutedEventArgs e)
{
  List<BugInfo> bugInfoList = LoadBugInfo("..\\..\\BugInfo.txt");
  ...

Dichiarato un oggetto elenco generico, bugInfoList e compilato l'elenco con i dati fittizi nel file BugInfo.txt utilizzando un metodo di supporto definito dal programma denominato LoadBugInfo. Per organizzare le informazioni sull'errore, dichiarata una classe helper piccoli, BugInfo, viene illustrato come Figura 5 .

Figura 5 di The BugInfo classe helper

public class BugInfo {
  public DateTime date;
  public int numberOpen;
  public int numberClosed;

  public BugInfo(DateTime date, int numberOpen, int numberClosed) {
    this.date = date;
    this.numberOpen = numberOpen;
    this.numberClosed = numberClosed;
  }
}

Dichiarato i tre dati campi come tipo pubblico per semplicità, anziché come tipo privata combinati con ottenere e impostare proprietà. Essendo BugInfo solo i dati, sarebbe una struttura C# è utilizzato invece di una classe. Metodo LoadBugInfo apre il file BugInfo.txt e scorre, l'analisi di ogni campo, quindi crea un'istanza di un oggetto BugInfo e memorizza ogni oggetto BugInfo in un elenco di risultati, come illustrato in di Figura 6.

Figura 6 Il metodo LoadBugInfo

private static List<BugInfo> LoadBugInfo(string fileName)
{
  var result = new List<BugInfo>();
  FileStream fs = new FileStream(fileName, FileMode.Open);
  StreamReader sr = new StreamReader(fs);
     
  string line = "";
  while ((line = sr.ReadLine()) != null)
  {
    string[] pieces = line.Split(':');
    DateTime d = DateTime.Parse(pieces[0]);
    int numopen = int.Parse(pieces[1]);
    int numclosed = int.Parse(pieces[2]);
    BugInfo bi = new BugInfo(d, numopen, numclosed);
    result.Add(bi);
  }
  sr.Close();
  fs.Close();
  return result;
}

Anziché la lettura e l'elaborazione di ciascuna riga del file di dati, che potesse avere letto tutte le righe in una matrice di stringa utilizzando il metodo File.ReadAllLines. Si noti che entrambe per mantenere le dimensioni del mio codice di piccole dimensioni e per maggiore chiarezza, omesso il normale controllo errori necessarie in un ambiente di produzione.

Successivamente dichiarato e assegnati valori a tre matrici, come si può vedere in di Figura 7.

Figura 7 Building matrici

DateTime[] dates = new DateTime[bugInfoList.Count];
  int[] numberOpen = new int[bugInfoList.Count];
  int[] numberClosed = new int[bugInfoList.Count];

  for (int i = 0; i < bugInfoList.Count; ++i)
  {
    dates[i] = bugInfoList[i].date;
    numberOpen[i] = bugInfoList[i].numberOpen;
    numberClosed[i] = bugInfoList[i].numberClosed;
  }
  ...

Quando si utilizza la libreria DynamicDataDisplay, spesso è utile organizzare i dati di visualizzazione in una serie di matrici unidimensionali. Come alternativa alla struttura programma, leggere i dati in un oggetto elenco e quindi trasferita i dati nelle matrici, potesse avere leggere i dati direttamente nelle matrici.

Le matrici di dati successivamente convertiti in tipi EnumerableDataSource speciali:

var datesDataSource = new EnumerableDataSource<DateTime>(dates);
datesDataSource.SetXMapping(x => dateAxis.ConvertToDouble(x));

var numberOpenDataSource = new EnumerableDataSource<int>(numberOpen);
numberOpenDataSource.SetYMapping(y => y);

var numberClosedDataSource = new EnumerableDataSource<int>(numberClosed);
numberClosedDataSource.SetYMapping(y => y);
...

Libreria DynamicDataDisplay essere graphed di tutti i dati devono essere in un formato uniforme. Tre matrici di dati semplicemente passato al costruttore EnumerableDataSource generico. Inoltre, la libreria deve essere indicata quale asse, x o y , è associato a ciascuna origine dati. I metodi SetXMapping e SetYMapping accettano delegati di metodi come argomenti. Anziché definire delegati espliciti, espressioni lambda utilizzato per creare i metodi anonimi. Tipo di dati fondamentale asse della libreria DynamicDataDisplay è double. I metodi SetXMapping e SetYMapping mappare i dati specifici di tipo double.

Sulla x -asse, utilizzato il metodo ConvertToDouble per convertire esplicitamente i dati DateTime nel tipo double. Nel y -asse, ho semplicemente scritto y = > y (lettura come “ y va y ”) per convertire in modo implicito input int y y double output. Potrebbe sono stata esplicita con il mapping dei tipi scrivendo SetYMapping(y => Convert.ToDouble(y). Le scelte di x e y di per parametri ’ le espressioni lambda sono arbitrarie, sarebbe stato utilizzato alcun nome di parametro.

Il passaggio successivo è stato per combinare le origini dati di x - asse e y - asse:

CompositeDataSource compositeDataSource1 = new
  CompositeDataSource(datesDataSource, numberOpenDataSource);

CompositeDataSource compositeDataSource2 = new
  CompositeDataSource(datesDataSource, numberClosedDataSource);

...

Schermata di di Figura 1 Mostra due serie di dati, ovvero il numero di bug aperti e il numero di bug di chiusura, ovvero tracciati nel grafico stesso. Ogni origine dati composito definisce quindi una serie di dati, qui necessari due origini di dati, uno per il numero di bug aperti e uno per il numero di bug di chiusura. I dati tutti pronti, una singola istruzione tracciati effettivamente i punti dati:

plotter.AddLineGraph(compositeDataSource1,
  new Pen(Brushes.Blue, 2),
  new CirclePointMarker { Size = 10.0, Fill = Brushes.Red },
  new PenDescription("Number bugs open"));

...

Il metodo AddLineGraph accetta un CompositeDataSource che definisce i dati vengano tracciati, insieme alle informazioni sulle modalità di tracciare. Qui indicato il plotter objectnamed plotter (defined in the Window1.xaml file) per eseguire le operazioni seguenti: disegnare un grafico con una linea blu di spessore 2, luogo a indicatori circolari di dimensione 10 che hanno bordi rossi e riempimento rosso e aggiungere serie titolo numero bug aprire . Utile, vero? Una delle numerose alternative, sarebbe stato utilizzato

plotter.AddLineGraph(compositeDataSource1, Colors.Red, 1, "Number Open")

Per disegnare una linea sottile di colore rossa con senza indicatori. O potrebbe avere creato una linea tratteggiata anziché una linea continua:

Pen dashedPen = new Pen(Brushes.Magenta, 3);
dashedPen.DashStyle = DashStyles.DashDot;
plotter.AddLineGraph(compositeDataSource1, dashedPen,
  new PenDescription("Open bugs"));

Programma terminato da tracciare la seconda serie di dati:

... 
    plotter.AddLineGraph(compositeDataSource2,
    new Pen(Brushes.Green, 2),
    new TrianglePointMarker { Size = 10.0,
      Pen = new Pen(Brushes.Black, 2.0),
      Fill = Brushes.GreenYellow },
    new PenDescription("Number bugs closed"));

  plotter.Viewport.FitToView();

} // Window1_Loaded()

Qui indicato il plotter per utilizzare una linea verde con indicatori triangolari che hanno un bordo nero e un riempimento giallo verde. Metodo FitToView Ridimensiona diagramma alle dimensioni della finestra di WPF.

Dopo che indicano a Visual Studio per generare il progetto BugGraph, viene visualizzato un BugGraph.exe eseguibile, che può essere avviata in manualmente o a livello di codice in qualsiasi momento. È possibile aggiornare i dati sottostanti modificando semplicemente il file BugInfo.txt. Poiché l'intero sistema è basato su codice .NET Framework, è possibile integrare facilmente funzionalità grafico in qualsiasi progetto WPF senza dover affrontare i problemi tra tecnologia. Ed è disponibile una versione di Silverlight della libreria DynamicDataDisplay, pertanto è possibile aggiungere a livello di programmazione grafici alle applicazioni Web, troppo.

Un tracciato a dispersione

La tecnica che è presentata nella sezione precedente può essere applicata a qualsiasi tipo di dati, non solo test relativi dati. Let’s esaminiamo breve un altro esempio di semplice ma piuttosto impressionante. Nella schermata in di Figura 8 Mostra 13,509 statunitense Città.

Figure 8 Scatter Plot Example
Figura 8 esempio di traccia di dispersione

È probabilmente possibile identificare dove sono Florida, Texas, California del Sud e Great Lakes. I dati per la dispersione ottenuto da una raccolta di dati può essere utilizzato con il problema del passaggio venditore ( www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95 ), uno dei più famosi e ampiamente studied argomenti in informatica. File utilizzato, usa13509.tsp.gz, aspetto:

NAME : usa13509
(other header information)
1 245552.778 817827.778
2 247133.333 810905.556
3 247205.556 810188.889
...

13507 489663.889 972433.333
13508 489938.889 1227458.333
13509 490000.000 1222636.111

Il primo campo è un ID di indice a base 1. I campi secondo e terzo indicano le coordinate derivate dalla latitudine e longitudine us città con la popolazione pari o superiore a 500. Creata una nuova applicazione WPF, come descritto nella sezione precedente, al progetto viene aggiunto un elemento del file di testo e copiati nel file di dati Città. È impostata come commento le righe di intestazione del file di dati, anteponendo la doppia barra (/ /) caratteri a tali righe.

Per creare la dispersione illustrato in di Figura 8, è stato necessario apportare piccole modifiche all'esempio presentati nella sezione precedente. I membri della classe MapInfo è stato modificato come segue:

public int id;
  public double lat;
  public double lon;

Figura 9 indica la chiave loop nel metodo LoadMapInfo rivisto di elaborazione.

Figura 9 loop per traccia XY

while ((line = sr.ReadLine()) != null)
{
  if (line.StartsWith("//"))
    continue;
  else {
    string[] pieces = line.Split(' ');
    int id = int.Parse(pieces[0]);
    double lat = double.Parse(pieces[1]);  
    double lon = -1.0 * double.Parse(pieces[2]);  
    MapInfo mi = new MapInfo(id, lat, lon);
    result.Add(mi);
  }
}

Il codice è stato verificare se la riga corrente inizia con il token di commento definito dal programma e in tal caso, andare su di esso. Si noti che moltiplicato il campo derivato longitudine-1,0 quanto longitudine da est a Ovest (o da destra a sinistra) insieme al x -asse. Senza il -1,0 fattore, la mappa è un'immagine speculare dell'orientamento corretto.

Quando miei matrici di dati non elaborati, tutto è stato necessario eseguire era garantire correttamente associato latitudine e longitudine all'asse di - asse e la x - y, rispettivamente:

for (int i = 0; i < mapInfoList.Count; ++i)
{
  ids[i] = mapInfoList[i].id;
  xs[i] = mapInfoList[i].lon;
  ys[i] = mapInfoList[i].lat;
}

Se avevo invertito l'ordine delle associazioni, mappa risultante sarebbe è stata inclinata sul relativo bordo. Quando tracciati i dati, è necessario solo un piccolo tweak per rendere una dispersione tracciato invece di un grafico a linee:

plotter.AddLineGraph(compositeDataSource,
  new Pen(Brushes.White, 0),
  new CirclePointMarker { Size = 2.0, Fill = Brushes.Red },
  new PenDescription("U.S. cities"));

Passando un valore 0 al costruttore Pen specificato una riga 0 larghezza, rimossa la riga in modo efficace e creata una dispersione anziché un grafico a linee. Il grafico risultante è piuttosto interessante e il programma che ha generato il grafico ha impiegato solo pochi minuti per scrivere. Si ritiene, ho tentato di molti altri approcci per tracciare dati geografici e l'utilizzo di WPF con la libreria DynamicDataDisplay è tra le migliori soluzioni che ho trovato.

Creazione di grafici semplificazione

Le tecniche presentate in questo articolo possono essere utilizzate per generare a livello di programmazione di grafici. La chiave per la tecnica è la libreria DynamicDataDisplay da Microsoft Research. Quando viene utilizzato come una tecnica autonomo per generare grafici in un ambiente di produzione di software, l'approccio è particolarmente utile se i dati sottostanti vengono modificati frequentemente. Quando viene utilizzato in un'applicazione come tecnica di integrato per generare grafici, l'approccio è particolarmente utile con le applicazioni WPF o Silverlight. E come queste due tecnologie si evolvono, sono certo che vedremo più librerie visualizzazione visiva grande basate su di essi.

Dr. James McCaffrey lavora per Volt Information Sciences Inc. dove gestisce la formazione tecnica degli ingegneri software funzioni Microsoft Redmond, WA, campus. Si è occupato di numerosi prodotti Microsoft, inclusi Internet Explorer e MSN Search. McCaffrey è autore di “ .NET Test Automation Recipes: Un soluzione del problema dell'approccio ” (Apress, 2006). È possibile contattarlo all'indirizzo jammc@microsoft.com.