using System;
using System.Globalization;
using System.Web;
using System.Drawing.Imaging;
using GenLogic;
namespace GraphHandler
{
public class GraphHandler : IHttpHandler
{
private static Object init_lock = new Object();
private static GlgHttpRequestProcessor request_processor = null;
// GLG graph types used in the demo.
const int BAR_GRAPH = 0;
const int LINE_GRAPH = 1;
const int FILLED_LINE_GRAPH = 2;
const int MULTI_LINE_GRAPH = 3;
const int STACKED_BAR_GRAPH = 4;
// Inner class to keep all properties of the loaded graph object.
class GraphData
{
internal GlgObject graph; // Drawing
internal int glg_graph_type; // GLG graph type
internal char graph_time_axis; // 'X' for horiz. graphs or 'Y' for vert.
internal int num_values; // Number of values in a multi-value graphs.
internal GlgDemoDataSource datasource; // Graph's datasource.
}
/* The servlet handles several graph types, using a different drawing
for each graph.
*/
const int NUM_GRAPHS = 5;
// Drawing filenames.
static String [] drawing_names =
{ "graph1.g", "graph2.g", "graph3.g", "graph4.g", "graph5.g" };
static String app_path = "GraphHandler";
// Arrays to keep loaded graph drawing objects and their properties.
static GraphData [] graph_array = new GraphData[ NUM_GRAPHS ];
// Legend labels for multi-value graphs.
static String [] multi_line_labels = { "server1", "server2", "server3" };
static String [] stacked_bar_labels = { ".com", ".net", "other" };
///////////////////////////////////////////////////////////////////
public bool IsReusable
{
get { return false; }
}
///////////////////////////////////////////////////////////////////
public void ProcessRequest( HttpContext context )
{
lock( init_lock )
{
/* First time only: create GlgHttpRequestProcessor that properly
handles ASP.NET synchronization context. ProcessRequestData is
registered as a custom application-specific method used by
the request processor to process HTTP requests.
*/
if( request_processor == null )
request_processor =
new GlgHttpRequestProcessor( ProcessRequestData );
}
/* Request processor will invoke ProcessRequestData method to
handle application-specific logic.
*/
GlgHttpRequestData request_data =
request_processor.ProcessRequest( context );
/* Returned data contains an image or HTML text response.
If there were request processing errors, the errors will be
reported in the HTML output and returned data will be null.
*/
if( request_data != null )
{
if( request_data.image != null )
{
// Save image.
context.Response.ContentType = "image/png";
request_data.image.Save( context.Response.OutputStream,
ImageFormat.Png );
}
else
context.Response.Write( "Error: Unexpected response.
");
}
}
///////////////////////////////////////////////////////////////////
// A custom method invoked to process HTTP context.
//
// The HTTP context is passed in the request_data.context field.
// The generated image is assigned to the data.image field.
// The data.custom_data field may be used to pass additional
// custom data to the ProcessRequest method.
//
// The GlgObject.Error method may be used in the code to flag errors.
// Any errors will be reported in the generated HTML by the default
// GLG error handler.
//
// A custom GLG error handler may be set using the
// GlgObject.SetErrorHandler method inside this method.
// The request_data.got_errors field may be set to true to flag errors
// in case if a custom error handler is used.
///////////////////////////////////////////////////////////////////
void ProcessRequestData( GlgHttpRequestData request_data )
{
HttpRequest request = request_data.context.Request;
// Get requested width/height of the image.
int width = GetIntegerParameter( request, "width", 500 );
int height = GetIntegerParameter( request, "height", 300 );
// Limit max. size to avoid running out of heap space creating an image.
if( width > 1000 ) width = 1000;
if( height > 1000 ) height = 1000;
// Get graph type.
int graph_type = GetIntegerParameter( request, "graph_type", 0 );
if( graph_type < 0 || graph_type >= NUM_GRAPHS )
{
/* Uncomment error message to display an error instead of using
graph_type = 0.
*/
//Error( "Invalid graph type." );
graph_type = 0;
}
GlgObject graph;
GraphData graph_data = graph_array[ graph_type ];
/* Load the drawing just once and share it between all instances of
this HTTP handler.
*/
if( graph_data == null ) // First time: load the drawing.
{
GlgObject.Init();
String drawing_name = drawing_names[ graph_type ];
String path;
String dir = request_data.context.Server.MapPath( "~" );
if( dir.IndexOf( app_path ) == -1 )
path = dir + app_path + "\\" + drawing_name;
else
path = dir + "\\" + drawing_name;
graph = LoadDrawing( path );
graph.SetImageSize( width, height );
// Set graph's properties and create a demo datasource to update it.
graph_data = InitializeGraph( graph, graph_type );
// Store the drawing and graph data for reuse.
graph_array[ graph_type ] = graph_data;
}
else // Already loaded, reuse the drawing.
{
graph = graph_data.graph;
graph.SetImageSize( width, height );
}
// Use title from the request if supplied as a parameter.
String title = request.QueryString[ "title" ];
if( title != null )
graph.SetSResource( "Title/String", title );
else
graph.SetSResource( "Title/String", "Server Load" );
// Push new data into the graph.
UpdateGraphData( graph_type );
// Setup after data update to prepare to generate image.
graph.SetupHierarchy();
// Generate Image.
request_data.image = graph.CreateImage( null );
}
/////////////////////////////////////////////////////////////////
// Helper methods
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
int GetIntegerParameter( HttpRequest request, String name,
int default_value )
{
String parameter_string = request.QueryString[ name ];
if( parameter_string == null || parameter_string.Equals( "" ) )
return default_value;
try
{
return int.Parse( parameter_string, CultureInfo.InvariantCulture );
}
catch( Exception )
{
Error( "Invalid parameter value for: " + name +
" = " + parameter_string );
return default_value;
}
}
/////////////////////////////////////////////////////////////////
// Can be used only in ProcessRequestData() or methods
// originating from it.
/////////////////////////////////////////////////////////////////
void Error( String message )
{
GlgObject.Error( GlgErrorType.USER_ERROR, message, null );
}
/////////////////////////////////////////////////////////////////
GlgObject LoadDrawing( String drawing_path )
{
GlgObject drawing;
drawing = GlgObject.LoadWidget( drawing_path, GlgMediumType.FILE );
if( drawing == null )
{
Error( "Can't load drawing: " + drawing_path );
return null;
}
// Disable drawing border in the image: let html define it if needed.
drawing.SetDResource( "LineWidth", 0.0 );
return drawing;
}
/////////////////////////////////////////////////////////////////
// Demonstrates how to set various graph properties:
// number of datasamples, labels, major and minor ticks, etc.
// Alternatively, all graph properties other than the data may be
// defined in the drawing.
/////////////////////////////////////////////////////////////////
GraphData InitializeGraph( GlgObject graph, int graph_index )
{
// Hardcoded data just to demo a graph
const int num_major_ticks = 5;
const int num_minor_ticks = 4;
const double low = 0.0;
const double high = 100.0;
GraphData graph_data = new GraphData();
graph_data.graph = graph;
DetermineGraphType( graph_data );
int num_datasamples = num_major_ticks * num_minor_ticks;
// Number of labels and major ticks.
graph.SetDResource( "XLabelGroup/Factor", num_major_ticks );
// Number of minor ticks.
graph.SetDResource( "XLabelGroup/MinorFactor", num_minor_ticks );
/* Set the number of datasamples,
should match num_major_tick * num_minor_ticks.
*/
switch( graph_data.glg_graph_type )
{
default:
graph.SetDResource( "DataGroup/Factor", num_datasamples );
break;
case MULTI_LINE_GRAPH:
graph.SetDResource( "DataGroupOne/DataGroup/Factor",
num_datasamples );
break;
}
// Set the range of graph's value axis.
if( graph_data.graph_time_axis == 'X' ) // Value axis is Y
{
graph.SetDResource( "YLabelGroup/YLabel/Low", low );
graph.SetDResource( "YLabelGroup/YLabel/High", high );
}
else // Value axis is X
{
graph.SetDResource( "XLabelGroup/XLabel/Low", low );
graph.SetDResource( "XLabelGroup/XLabel/High", high );
}
/* For multi-line graph, the range each line may be different and
is set separately from the range of the value axis.
*/
if( graph_data.glg_graph_type == MULTI_LINE_GRAPH )
{
graph.SetDResource( "DataGroupOne/DataGroup/Marker/DataSample/Low",
low );
graph.SetDResource( "DataGroupOne/DataGroup/Marker/DataSample/High",
high );
}
// Set initial labels to ""
if( graph_data.graph_time_axis == 'X' )
graph.SetSResource( "XLabelGroup/XLabel/String", "" );
else
graph.SetSResource( "YLabelGroup/YLabel/String", "" );
// A value outside the graph.
double out_value = low - ( high - low );
// Set initial values.
switch( graph_data.glg_graph_type )
{
default:
case BAR_GRAPH:
graph.SetDResource( "DataGroup/DataSample/Value", low );
break;
case LINE_GRAPH:
graph.SetDResource( "DataGroup/Marker/DataSample/Value", out_value );
break;
case FILLED_LINE_GRAPH:
graph.SetDResource( "DataGroup/Marker/DataSample/Value", low );
break;
case MULTI_LINE_GRAPH:
graph.SetDResource( "DataGroupOne/DataGroup/Marker/DataSample/Value",
out_value );
break;
case STACKED_BAR_GRAPH:
graph.SetDResource( "DataGroup/Pack/DataSample/Low", low );
break;
}
// Set title and X/Y axis labels
graph.SetSResource( "Title/String", "Server Load" );
if( graph_data.graph_time_axis == 'X' )
{
graph.SetSResource( "YAxisLabel/String", "Load, %" );
graph.SetSResource( "XAxisLabel/String", "Time" );
}
else
{
graph.SetSResource( "XAxisLabel/String", "Time" );
graph.SetSResource( "YAxisLabel/String", "Load, %" );
}
// Set legend for multi-value graphs (stacked bar and multi-line)
if( graph_data.num_values > 1 )
for( int i=0; i