/*
 * rbcGraph.c --
 *
 *      This module implements a graph widget for the rbc toolkit.
 *
 * Copyright (c) 2001 BLT was created by George Howlett.
 * Copyright (c) 2009 RBC was created by Samuel Green, Nicholas Hudson, Stanton Sievers, Jarrod Stormo
 * Copyright (c) 2018 Rene Zaumseil
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

/*
 * To do:
 *
 * 5) Surface, contour, and flow graphs
 *
 * 7) Arrows for line markers
 *
 */

#include "tkoGraph.h"

Tk_Uid rbcXAxisUid;
Tk_Uid rbcYAxisUid;
Tk_Uid rbcBarElementUid;
Tk_Uid rbcLineElementUid;
Tk_Uid rbcStripElementUid;
Tk_Uid rbcContourElementUid;
Tk_Uid rbcLineMarkerUid;
Tk_Uid rbcBitmapMarkerUid;
Tk_Uid rbcImageMarkerUid;
Tk_Uid rbcTextMarkerUid;
Tk_Uid rbcPolygonMarkerUid;
Tk_Uid rbcWindowMarkerUid;

extern Tk_CustomOption rbcLinePenOption;
extern Tk_CustomOption rbcBarPenOption;
extern Tk_CustomOption rbcDistanceOption;
extern Tk_CustomOption rbcBarModeOption;
extern Tk_CustomOption rbcPadOption;
extern Tk_CustomOption rbcTileOption;
extern Tk_CustomOption rbcShadowOption;
extern Tk_CustomOption rbcStyleOption;

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

#define DEF_GRAPH_ASPECT_RATIO      "0.0"
#define DEF_GRAPH_BAR_BASELINE      "0.0"
#define DEF_GRAPH_BAR_MODE          "normal"
#define DEF_GRAPH_BAR_WIDTH         "0.8"
#define DEF_GRAPH_BACKGROUND        RBC_NORMAL_BACKGROUND
#define DEF_GRAPH_BG_MONO           RBC_NORMAL_BG_MONO
#define DEF_GRAPH_BORDERWIDTH       RBC_BORDERWIDTH
#define DEF_GRAPH_BUFFER_ELEMENTS   "1"
#define DEF_GRAPH_BUFFER_GRAPH	    "1"
#define DEF_GRAPH_CURSOR            "crosshair"
#define DEF_GRAPH_FONT              RBC_FONT_LARGE
#define DEF_GRAPH_HALO              "2m"
#define DEF_GRAPH_HALO_BAR          "0.1i"
#define DEF_GRAPH_HEIGHT            "4i"
#define DEF_GRAPH_HIGHLIGHT_BACKGROUND  RBC_NORMAL_BACKGROUND
#define DEF_GRAPH_HIGHLIGHT_BG_MONO RBC_NORMAL_BG_MONO
#define DEF_GRAPH_HIGHLIGHT_COLOR   "black"
#define DEF_GRAPH_HIGHLIGHT_WIDTH   "2"
#define DEF_GRAPH_INVERT_XY         "0"
#define DEF_GRAPH_JUSTIFY           "center"
#define DEF_GRAPH_MARGIN            "0"
#define DEF_GRAPH_MARGIN_VAR        (char *)NULL
#define DEF_GRAPH_PLOT_BACKGROUND   "white"
#define DEF_GRAPH_PLOT_BG_MONO      "white"
#define DEF_GRAPH_PLOT_BW_COLOR     RBC_BORDERWIDTH
#define DEF_GRAPH_PLOT_BW_MONO      "0"
#define DEF_GRAPH_PLOT_PADX         "8"
#define DEF_GRAPH_PLOT_PADY         "8"
#define DEF_GRAPH_PLOT_RELIEF       "sunken"
#define DEF_GRAPH_RELIEF            "flat"
#define DEF_GRAPH_SHADOW_COLOR      (char *)NULL
#define DEF_GRAPH_SHADOW_MONO       (char *)NULL
#define DEF_GRAPH_SHOW_VALUES       "no"
#define DEF_GRAPH_TAKE_FOCUS        ""
#define DEF_GRAPH_TITLE             (char *)NULL
#define DEF_GRAPH_TITLE_COLOR       RBC_NORMAL_FOREGROUND
#define DEF_GRAPH_TITLE_MONO        RBC_NORMAL_FG_MONO
#define DEF_GRAPH_WIDTH             "5i"
#define DEF_GRAPH_DATA              (char *)NULL
#define DEF_GRAPH_DATA_COMMAND      (char *)NULL

static Tcl_IdleProc DisplayGraph;
static Tk_EventProc GraphEventProc;

static RbcBindPickProc PickEntry;
static RbcTileChangedProc TileChangedProc;
/*
* Methods
*/
static int GraphConstructor(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphDestructor(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_tko_configure(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_style(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_barmode(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_barwidth(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_plotpadx(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_plotpady(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_shadow(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int GraphMethod_tile(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);

/*
 * Functions
 */
static void AdjustAxisPointers(
    RbcGraph * graph);
static void DrawMargins(
    RbcGraph * graph,
    Drawable drawable);
static void DrawPlotRegion(
    RbcGraph * graph,
    Drawable drawable);
static void UpdateMarginTraces(
    RbcGraph * graph);
static int XAxisOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int X2AxisOp(
    RbcGraph* graph,
    Tcl_Interp* interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int YAxisOp(
    RbcGraph* graph,
    Tcl_Interp* interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int Y2AxisOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int BarOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int LineOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int ElementOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int ExtentsOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int InsideOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int InvtransformOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int TransformOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);
static int SnapOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[]);

/*
 * graphOptionDefine --
 *
 * Options and option methods created in class constructor.
 */
static Tko_WidgetOptionDefine graphOptionDefine[] = {
    {"-class", "class", "Class", "TkoGraph", TKO_OPTION_READONLY,
	NULL, TKO_SET_CLASS, 0},
    {"-style", "style", "Style", "line", TKO_OPTION_READONLY,
	GraphMethod_style, 0, 0},
    {"-aspect", "aspect", "Aspect", DEF_GRAPH_ASPECT_RATIO, 0,
	NULL, TKO_SET_DOUBLE, offsetof(RbcGraph, aspect)},
    {"-background", "background", "Background", DEF_GRAPH_BACKGROUND, 0,
	NULL, TKO_SET_3DBORDER, offsetof(RbcGraph, border)},
    {"-barmode", "barMode", "BarMode", DEF_GRAPH_BAR_MODE, 0,
	GraphMethod_barmode, 0, 0},
    {"-barwidth", "barWidth", "BarWidth", DEF_GRAPH_BAR_WIDTH, 0,
	GraphMethod_barwidth, 0, 0},
    {"-baseline", "baseline", "Baseline", DEF_GRAPH_BAR_BASELINE, 0,
	NULL, TKO_SET_DOUBLE, offsetof(RbcGraph, baseline)},
    {"-bg", "-background", NULL, NULL, 0, NULL, 0, 0},
    {"-bm", "-bottommargin", NULL, NULL, 0, NULL, 0, 0},
    {"-borderwidth", "borderWidth", "BorderWidth", DEF_GRAPH_BORDERWIDTH, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph, borderWidth)},
    {"-bd", "-borderwidth", NULL, NULL, 0, NULL, 0, 0},
    {"-bottommargin", "bottomMargin", "Margin", DEF_GRAPH_MARGIN, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph,margins[RBC_MARGIN_BOTTOM].reqSize)},
    {"-bottomvariable", "bottomVariable", "BottomVariable", DEF_GRAPH_MARGIN_VAR, TKO_OPTION_NULL,
	NULL, TKO_SET_STRING, offsetof(RbcGraph,margins[RBC_MARGIN_BOTTOM].varName)},
    {"-bufferelements", "bufferElements", "BufferElements", DEF_GRAPH_BUFFER_ELEMENTS, 0,
	NULL, TKO_SET_BOOLEAN, offsetof(RbcGraph, backingStore)},
    {"-buffergraph", "bufferGraph", "BufferGraph", DEF_GRAPH_BUFFER_GRAPH, 0,
	NULL, TKO_SET_BOOLEAN, offsetof(RbcGraph, doubleBuffer)},
    {"-cursor", "cursor", "Cursor", DEF_GRAPH_CURSOR, 0,
	NULL, TKO_SET_CURSOR, offsetof(RbcGraph, cursor)},
    {"-fg", "-foreground", NULL, NULL, 0, NULL, 0, 0},
    {"-font", "font", "Font", DEF_GRAPH_FONT, 0,
	NULL, TKO_SET_FONT, offsetof(RbcGraph, titleTextStyle.font)},
    {"-foreground", "foreground", "Foreground", DEF_GRAPH_TITLE_COLOR, 0,
	NULL, TKO_SET_XCOLOR, offsetof(RbcGraph, titleTextStyle.color)},
    {"-halo", "halo", "Halo", DEF_GRAPH_HALO, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph, halo)},
    {"-height", "height", "Height", DEF_GRAPH_HEIGHT, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph, reqHeight)},
    {"-highlightbackground", "highlightBackground", "HighlightBackground", DEF_GRAPH_HIGHLIGHT_BACKGROUND, 0,
	NULL, TKO_SET_XCOLOR, offsetof(RbcGraph, highlightBgColor)},
    {"-highlightcolor", "highlightColor", "HighlightColor", DEF_GRAPH_HIGHLIGHT_COLOR, 0,
	NULL, TKO_SET_XCOLOR, offsetof(RbcGraph, highlightColor)},
    {"-highlightthickness", "highlightThickness", "HighlightThickness", DEF_GRAPH_HIGHLIGHT_WIDTH, 0,
	NULL, TKO_SET_PIXEL, offsetof(RbcGraph, highlightWidth)},
    {"-invertxy", "invertXY", "InvertXY", DEF_GRAPH_INVERT_XY, 0,
	NULL, TKO_SET_BOOLEAN, offsetof(RbcGraph, inverted)},
    {"-justify", "justify", "Justify", DEF_GRAPH_JUSTIFY, 0,
	NULL, TKO_SET_JUSTIFY, offsetof(RbcGraph,titleTextStyle.justify)},
    {"-leftmargin", "leftMargin", "Margin", DEF_GRAPH_MARGIN, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph,margins[RBC_MARGIN_LEFT].reqSize)},
    {"-leftvariable", "leftVariable", "LeftVariable", DEF_GRAPH_MARGIN_VAR, TKO_OPTION_NULL,
	NULL, TKO_SET_STRING, offsetof(RbcGraph,margins[RBC_MARGIN_LEFT].varName)},
    {"-lm", "-leftmargin", NULL, NULL, 0, NULL, 0, 0},
    {"-plotbackground", "plotBackground", "Background", DEF_GRAPH_PLOT_BG_MONO, 0,
	NULL, TKO_SET_XCOLOR, offsetof(RbcGraph, plotBg)},
    {"-plotborderwidth", "plotBorderWidth", "BorderWidth", DEF_GRAPH_PLOT_BW_COLOR, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph,plotBorderWidth)},
    {"-plotpadx", "plotPadX", "PlotPad", DEF_GRAPH_PLOT_PADX, 0,
	GraphMethod_plotpadx, 0, 0},
    {"-plotpady", "plotPadY", "PlotPad", DEF_GRAPH_PLOT_PADY, 0,
	GraphMethod_plotpady, 0, 0},
    {"-plotrelief", "plotRelief", "Relief", DEF_GRAPH_PLOT_RELIEF, 0,
	NULL, TKO_SET_RELIEF, offsetof(RbcGraph, plotRelief)},
    {"-relief", "relief", "Relief", DEF_GRAPH_RELIEF, 0,
	NULL, TKO_SET_RELIEF, offsetof(RbcGraph, relief)},
    {"-rightmargin", "rightMargin", "Margin", DEF_GRAPH_MARGIN, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph,margins[RBC_MARGIN_RIGHT].reqSize)},
    {"-rightvariable", "rightVariable", "RightVariable", DEF_GRAPH_MARGIN_VAR, TKO_OPTION_NULL,
	NULL, TKO_SET_STRING, offsetof(RbcGraph,margins[RBC_MARGIN_RIGHT].varName)},
    {"-rm", "-rightmargin", NULL, NULL, 0, NULL, 0, 0},
    {"-shadow", "shadow", "Shadow", DEF_GRAPH_SHADOW_COLOR, 0,
	GraphMethod_shadow, 0, 0},
    {"-takefocus", "takeFocus", "TakeFocus", DEF_GRAPH_TAKE_FOCUS, TKO_OPTION_NULL,
	NULL, TKO_SET_STRING, offsetof(RbcGraph, takeFocus)},
    {"-tile", "tile", "Tile", NULL, 0,
	GraphMethod_tile, 0, 0},
    {"-title", "title", "Title", DEF_GRAPH_TITLE, TKO_OPTION_NULL,
	NULL, TKO_SET_STRING, offsetof(RbcGraph, title)},
    {"-tm", "-topmargin", NULL, NULL, 0, NULL, 0, 0},
    {"-topmargin", "-topmargin", "Margin", DEF_GRAPH_MARGIN, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph,margins[RBC_MARGIN_TOP].reqSize)},
    {"-topvariable", "topVariable", "TopVariable", DEF_GRAPH_MARGIN_VAR, TKO_OPTION_NULL,
	NULL, TKO_SET_STRING, offsetof(RbcGraph,margins[RBC_MARGIN_TOP].varName)},
    {"-width", "width", "Width", DEF_GRAPH_WIDTH, 0,
	NULL, TKO_SET_PIXELNONEGATIV, offsetof(RbcGraph, reqWidth)},
    {NULL, NULL, NULL, NULL, 0, NULL, 0, 0}
};

/*
 * graphMethods --
 *
 * Methods created in class constructor.
 */
static Tcl_MethodType2 graphMethods[] = {
    {TCL_OO_METHOD_VERSION_2, NULL, GraphConstructor, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, NULL, GraphDestructor, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "axis", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "bar", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "crosshairs", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "element", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "extents", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "grid", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "inside", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "invtransform", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "legend", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "line", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "marker", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "pen", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "postscript", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "snap", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "transform", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "x2axis", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "xaxis", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "y2axis", GraphMethod, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "yaxis", GraphMethod, NULL, NULL},
/*    {TCL_OO_METHOD_VERSION_2, "pdf", GraphMethodPdf, NULL, NULL},*/
    {-1, NULL, NULL, NULL, NULL},
    {TCL_OO_METHOD_VERSION_2, "_tko_configure", GraphMethod_tko_configure,
            NULL, NULL},
    {-1, NULL, NULL, NULL, NULL}
};

/*
 * Tko_GraphInit --
 *
 *  Initializer for the graph widget package.
 *
 * Results:
 *  A standard Tcl result.
 *
 * Side Effects:
 *  Tcl commands created
 */
int
Tko_GraphInit(
    Tcl_Interp * interp)
{
    Tcl_Obj *tmpPtr;
    int ret;

    rbcBarElementUid = Tk_GetUid("BarElement");
    rbcLineElementUid = Tk_GetUid("LineElement");
    rbcStripElementUid = Tk_GetUid("StripElement");
    rbcContourElementUid = Tk_GetUid("ContourElement");

    rbcLineMarkerUid = Tk_GetUid("LineMarker");
    rbcBitmapMarkerUid = Tk_GetUid("BitmapMarker");
    rbcImageMarkerUid = Tk_GetUid("ImageMarker");
    rbcTextMarkerUid = Tk_GetUid("TextMarker");
    rbcPolygonMarkerUid = Tk_GetUid("PolygonMarker");
    rbcWindowMarkerUid = Tk_GetUid("WindowMarker");

    rbcXAxisUid = Tk_GetUid("X");
    rbcYAxisUid = Tk_GetUid("Y");

    /*
     * Add methods and options
     */
    tmpPtr = Tcl_NewStringObj("::tko::graph",-1);
    Tcl_IncrRefCount(tmpPtr);
    ret = Tko_WidgetClassDefine(interp, tmpPtr,
            graphMethods, graphOptionDefine);
    Tcl_DecrRefCount(tmpPtr);
    if (ret != TCL_OK) {
        return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 * GraphConstructor --
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphConstructor(
    ClientData clientData,
    Tcl_Interp* interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    Tcl_Object object;
    RbcGraph* graph;
    int skip;
    Tcl_Obj* classname;
    Tcl_Obj* myArglist;

    /* Get current object. Should not fail? */
    if ((object = Tcl_ObjectContextObject(context)) == NULL) {
        return TCL_ERROR;
    }

    /*
     * Create and initialize the graph data structure.
     */
    graph = ckalloc(sizeof(RbcGraph));
    assert(graph);
    memset(graph, 0, sizeof(RbcGraph));
    graph->classUid = rbcLineElementUid;
    graph->chartStyle = "line";
    graph->flags = (RBC_RESET_WORLD);
    graph->cursor = NULL;
    graph->inset = 0;
    graph->borderWidth = 0;
    graph->relief = TK_RELIEF_FLAT;
    graph->highlightWidth = 2;
    graph->border = NULL;
    graph->highlightBgColor = NULL;
    graph->highlightColor = NULL;
    graph->title = NULL;
    graph->titleX = graph->titleY = 0;
    RbcInitTextStyle(&graph->titleTextStyle);
    graph->takeFocus = NULL;
    graph->reqWidth = graph->reqHeight = 0;
    graph->width = graph->height = 0;
    Tcl_InitHashTable(&graph->penTable, TCL_STRING_KEYS);
    Tcl_InitHashTable(&graph->axes.table, TCL_STRING_KEYS);
    Tcl_InitHashTable(&graph->axes.tagTable, TCL_STRING_KEYS);
    Tcl_InitHashTable(&graph->elements.table, TCL_STRING_KEYS);
    Tcl_InitHashTable(&graph->elements.tagTable, TCL_STRING_KEYS);
    Tcl_InitHashTable(&graph->markers.table, TCL_STRING_KEYS);
    Tcl_InitHashTable(&graph->markers.tagTable, TCL_STRING_KEYS);
    graph->elements.displayList = RbcChainCreate();
    graph->markers.displayList = RbcChainCreate();
    graph->axes.displayList = RbcChainCreate();
    graph->classUid = NULL;
    graph->chartStyle = NULL;
    graph->bindTable = NULL;
    graph->nextMarkerId = 1;
    graph->axisChain[0] = NULL; /* set in RbcDefaultAxes() */
    graph->axisChain[1] = NULL; /* set in RbcDefaultAxes() */
    graph->axisChain[2] = NULL; /* set in RbcDefaultAxes() */
    graph->axisChain[3] = NULL; /* set in RbcDefaultAxes() */
    graph->margins[RBC_MARGIN_BOTTOM].site = RBC_MARGIN_BOTTOM;
    graph->margins[RBC_MARGIN_LEFT].site = RBC_MARGIN_LEFT;
    graph->margins[RBC_MARGIN_TOP].site = RBC_MARGIN_TOP;
    graph->margins[RBC_MARGIN_RIGHT].site = RBC_MARGIN_RIGHT;
    graph->postscript = NULL;
    graph->legend = NULL;
    graph->crosshairs = NULL;
    graph->gridPtr = NULL;
    graph->halo = 0;
    graph->inverted = 0;
    graph->tile = NULL;
    graph->drawGC = NULL;
    graph->fillGC = NULL;
    graph->plotBorderWidth = 0;
    graph->plotRelief = TK_RELIEF_SUNKEN;
    graph->plotBg = NULL;
    graph->plotFillGC = NULL;
    graph->aspect = 0.0;
    graph->left = graph->right = 0;
    graph->top = graph->bottom = 0;
    graph->padX.side1 = graph->padX.side2 = 8;
    graph->vRange = graph->vOffset = 0;
    graph->padY.side1 = graph->padY.side2 = 8;
    graph->hRange = graph->hOffset = 0;
    graph->vScale = graph->hScale = 0.;
    graph->doubleBuffer = TRUE;
    graph->backingStore = TRUE;
    graph->backPixmap = None;
    graph->backWidth = graph->backHeight = 0;
    graph->baseline = 0.;
    graph->barWidth = 0.;
    graph->mode = MODE_INFRONT;
    graph->freqArr = NULL;
    /*      Tcl_HashTable   freqTable; */
    graph->nStacks = 0;

    skip = Tcl_ObjectContextSkippedArgs(context);
    myArglist = Tcl_NewListObj(objc - skip, &objv[skip]);
    Tcl_IncrRefCount(myArglist);
    classname = Tcl_NewStringObj("::tko::graph", -1);
    Tcl_IncrRefCount(classname);
    if (Tko_WidgetCreate(&(graph->widget), interp, object, classname, 0,
        myArglist) != TCL_OK) {
        Tcl_DecrRefCount(myArglist);
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    Tcl_DecrRefCount(myArglist);

    ((TkWindow*)graph->widget.tkWin)->instanceData = graph;

    /*
     * Init pens
     */
    if (RbcCreatePen(graph, "activeLine", rbcLineElementUid, 0,
        NULL) == NULL) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    if (RbcCreatePen(graph, "activeBar", rbcBarElementUid, 0,
        NULL) == NULL) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    /*
     * Create axis
     */
    if (RbcDefaultAxes(graph) != TCL_OK) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    AdjustAxisPointers(graph);

    if (RbcCreatePostScript(graph) != TCL_OK) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    if (RbcCreateCrosshairs(graph) != TCL_OK) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    if (RbcCreateLegend(graph) != TCL_OK) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    if (RbcCreateGrid(graph) != TCL_OK) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    Tk_CreateEventHandler(graph->widget.tkWin,
        ExposureMask | StructureNotifyMask | FocusChangeMask, GraphEventProc,
        graph);

    graph->bindTable =
        RbcCreateBindingTable(interp, graph->widget.tkWin, graph, PickEntry);

    /* call "my _tko_init classname" */
    if (Tko_WidgetOptionInit(interp, &graph->widget, classname) != TCL_OK) {
        Tcl_DecrRefCount(classname);
        return TCL_ERROR;
    }
    Tcl_DecrRefCount(classname);
    /* No need to set return value. It will be ignored by "oo::class create" */
    return TCL_OK;
}

/*
 * GraphDestructor --
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphDestructor(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
	Tko_Widget *widget;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) != NULL) {
		RbcGraph *graph = (RbcGraph *)widget;
		Tk_Window tkWin = widget->tkWin;
        Tcl_Preserve(graph);

        if(tkWin) {
            Tk_DeleteEventHandler(tkWin,
                ExposureMask | StructureNotifyMask | FocusChangeMask,
                GraphEventProc, graph);
        }

		if (graph->flags & RBC_REDRAW_PENDING) {
			Tcl_CancelIdleCall(DisplayGraph, graph);
		}
		if (graph->border != NULL) {
			Tk_Free3DBorder(graph->border);
		}
		if (graph->highlightBgColor != NULL) {
			Tk_FreeColor(graph->highlightBgColor);
		}
		if (graph->highlightColor != NULL) {
			Tk_FreeColor(graph->highlightColor);
		}
		if (graph->plotBg != NULL) {
			Tk_FreeColor(graph->plotBg);
		}
		/*
		* Destroy the individual components of the graph: elements, markers,
		* X and Y axes, legend, display lists etc.
		*/
		RbcDestroyMarkers(graph);
		RbcDestroyElements(graph);
		RbcDestroyAxes(graph);      /* take care of *axisChain */
		RbcDestroyPens(graph);

		if (graph->legend != NULL) {
			RbcDestroyLegend(graph);
		}
		if (graph->postscript != NULL) {
			RbcDestroyPostScript(graph);
		}
		if (graph->crosshairs != NULL) {
			RbcDestroyCrosshairs(graph);
		}
		if (graph->gridPtr != NULL) {
			RbcDestroyGrid(graph);
		}
		if (graph->bindTable != NULL) {
			RbcDestroyBindingTable(graph->bindTable);
		}

		/* Release allocated X resources and memory. */
		if (graph->widget.display != NULL) {
			if (graph->cursor != NULL) {
				Tk_FreeCursor(graph->widget.display, graph->cursor);
			}
			if (graph->drawGC != NULL) {
				Tk_FreeGC(graph->widget.display, graph->drawGC);
			}
			if (graph->fillGC != NULL) {
				Tk_FreeGC(graph->widget.display, graph->fillGC);
			}
			if (graph->plotFillGC != NULL) {
				Tk_FreeGC(graph->widget.display, graph->plotFillGC);
			}
			RbcFreeTextStyle(graph->widget.display, &graph->titleTextStyle);
			if (graph->backPixmap != None) {
				Tk_FreePixmap(graph->widget.display, graph->backPixmap);
			}
		}
		if (graph->freqArr != NULL) {
			ckfree((char *)graph->freqArr);
		}
		if (graph->title != NULL) {
			ckfree(graph->title);
		}
		if (graph->takeFocus != NULL) {
			ckfree(graph->takeFocus);
		}
		if (graph->nStacks > 0) {
			Tcl_DeleteHashTable(&graph->freqTable);
		}
		if (graph->tile != NULL) {
			RbcFreeTile(graph->tile);
		}

	    Tko_WidgetDestroy(context);
        Tcl_Release(graph);
    }

    return TCL_OK;
}

/*
* GraphMethodPdf --
*
*      Create pdf of graph.
*
* Results:
*      Pdf.
*
* Side effects:
*/
#ifdef TODO
static int
GraphMethodPdf(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
	Tko_Widget *widget;
    RbcGraph *graph;
	Tk_Window tkWin;
    int skip;/* including object and method call name */

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| (tkWin = widget->tkWin) == NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

	skip = Tcl_ObjectContextSkippedArgs(context);

    /*TODO Implementation missing*/
    return TCL_OK;
}
#endif

/*
* GraphMethod_tko_configure --
*
*      Allocates resources for the graph.
*
* Results:
*      None.
*
* Side effects:
*      Configuration information, such as text string, colors, font,
*      etc. get set for graph;  old resources get freed, if there
*      were any.  The graph is redisplayed.
*/
static int
GraphMethod_tko_configure(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    XColor *colorPtr;
    GC  newGC;
    XGCValues gcValues;
    unsigned long gcMask;
	Tko_Widget *widget;
    RbcGraph *graph;
	Tk_Window tkWin;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| (tkWin = widget->tkWin) == NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

    /* Don't allow negative bar widths. Reset to an arbitrary value (0.1) */
    if(graph->barWidth <= 0.0) {
        graph->barWidth = 0.1;
    }
    graph->inset = graph->borderWidth + graph->highlightWidth + 1;
    if((graph->reqHeight != Tk_ReqHeight(tkWin))
        || (graph->reqWidth != Tk_ReqWidth(tkWin))) {
        Tk_GeometryRequest(tkWin, graph->reqWidth, graph->reqHeight);
    }
    Tk_SetInternalBorder(tkWin, graph->borderWidth);
    colorPtr = Tk_3DBorderColor(graph->border);

    if(graph->title != NULL) {
    int w, h;

        RbcGetTextExtents(&graph->titleTextStyle, graph->title, &w, &h);
        graph->titleTextStyle.height = h + 10;
    } else {
        graph->titleTextStyle.width = graph->titleTextStyle.height = 0;
    }

    /*
     * Create GCs for interior and exterior regions, and a background
     * GC for clearing the margins with XFillRectangle
     */

    /* Margin GC */

    gcValues.foreground = graph->titleTextStyle.color->pixel;
    gcValues.background = colorPtr->pixel;
    gcMask = (GCForeground | GCBackground);
    newGC = Tk_GetGC(tkWin, gcMask, &gcValues);
    if(graph->drawGC != NULL) {
        Tk_FreeGC(graph->widget.display, graph->drawGC);
    }
    graph->drawGC = newGC;

    /* Plot fill GC (Background = Foreground) */

    gcValues.foreground = graph->plotBg->pixel;
    newGC = Tk_GetGC(tkWin, gcMask, &gcValues);
    if(graph->plotFillGC != NULL) {
        Tk_FreeGC(graph->widget.display, graph->plotFillGC);
    }
    graph->plotFillGC = newGC;

    /* Margin fill GC (Background = Foreground) */

    gcValues.foreground = colorPtr->pixel;
    gcValues.background = graph->titleTextStyle.color->pixel;
    newGC = Tk_GetGC(tkWin, gcMask, &gcValues);
    if(graph->fillGC != NULL) {
        Tk_FreeGC(graph->widget.display, graph->fillGC);
    }
    graph->fillGC = newGC;
    if(graph->tile != NULL) {
        RbcSetTileChangedProc(graph->tile, TileChangedProc, graph);
    }

    RbcResetTextStyle(tkWin, &graph->titleTextStyle);

    if(RbcConfigModified(configSpecs, "-invertxy", (char *)NULL)) {

        /*
         * If the -inverted option changed, we need to readjust the pointers
         * to the axes and recompute the their scales.
         */

        AdjustAxisPointers(graph);
        graph->flags |= RBC_RESET_AXES;
    }
    if((!graph->backingStore) && (graph->backPixmap != None)) {

        /*
         * Free the pixmap if we're not buffering the display of elements
         * anymore.
         */

        Tk_FreePixmap(graph->widget.display, graph->backPixmap);
        graph->backPixmap = None;
    }
    /*
     * Reconfigure the crosshairs, just in case the background color of
     * the plotarea has been changed.
     */
    RbcConfigureCrosshairs(graph);

    /*
     *  Update the layout of the graph (and redraw the elements) if
     *  any of the following graph options which affect the size of
     *  the plotting area has changed.
     *
     *      -aspect
     *      -borderwidth, -plotborderwidth
     *      -font, -title
     *      -width, -height
     *      -invertxy
     *      -bottommargin, -leftmargin, -rightmargin, -topmargin,
     *      -barmode, -barwidth
     */
    if(RbcConfigModified(configSpecs, "-invertxy", "-title", "-font",
            "-*margin", "-*width", "-height", "-barmode", "-*pad*", "-aspect",
            (char *)NULL)) {
        graph->flags |= RBC_RESET_WORLD;
    }
    if(RbcConfigModified(configSpecs, "-plotbackground", (char *)NULL)) {
        graph->flags |= RBC_REDRAW_BACKING_STORE;
    }
    graph->flags |= RBC_REDRAW_WORLD;
    RbcEventuallyRedrawGraph(graph);
    return TCL_OK;
}

/*
 * GraphMethod --
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    int cmdIndex, result;
    RbcOp proc;
    static const char *const graphCmdNames[] = {
        "axis", "bar",
        "crosshairs", "element", "extents", "grid",
        "inside", "invtransform", "legend", "line",
        "marker", "pen", "postscript",
        "snap", "transform",
        "x2axis", "xaxis", "y2axis", "yaxis",
        NULL
    };

    enum graphCmd {
        COMMAND_AXIS, COMMAND_BAR,
        COMMAND_CROSSHAIRS, COMMAND_ELEMENT, COMMAND_EXTENTS, COMMAND_GRID,
        COMMAND_INSIDE, COMMAND_INVTRANSFORM, COMMAND_LEGEND, COMMAND_LINE,
        COMMAND_MARKER, COMMAND_PEN, COMMAND_POSTSCRIPT,
        COMMAND_SNAP, COMMAND_TRANSFORM,
        COMMAND_X2AXIS, COMMAND_XAXIS, COMMAND_Y2AXIS, COMMAND_YAXIS
    };
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

    /*
     * Parse the widget command by looking up the second token in the list of
     * valid command names.
     */

    result = Tcl_GetIndexFromObj(interp, objv[1], graphCmdNames, "option", 0,
        &cmdIndex);
    if(result != TCL_OK) {
        return result;
    }

    switch ((enum graphCmd)cmdIndex) {
    case COMMAND_AXIS:{
        proc = (RbcOp) RbcVirtualAxisOp;
        break;
    }
    case COMMAND_BAR:{
        proc = BarOp;
        break;
    }
    case COMMAND_CROSSHAIRS:{
        proc = RbcCrosshairsOp;
        break;
    }
    case COMMAND_ELEMENT:{
        proc = ElementOp;
        break;
    }
    case COMMAND_EXTENTS:{
        if(objc != 3) {
            Tcl_WrongNumArgs(interp, 2, objv, "item");
            return TCL_ERROR;
        }
        proc = ExtentsOp;
        break;
    }
    case COMMAND_GRID:{
        proc = RbcGridOp;
        break;
    }
    case COMMAND_INSIDE:{
        if(objc != 4) {
            Tcl_WrongNumArgs(interp, 2, objv, "winX winY");
            return TCL_ERROR;
        }
        proc = InsideOp;
        break;
    }
    case COMMAND_INVTRANSFORM:{
        if(objc != 4) {
            Tcl_WrongNumArgs(interp, 2, objv, "winX winY");
            return TCL_ERROR;
        }
        proc = InvtransformOp;
        break;
    }
    case COMMAND_LEGEND:{
        proc = RbcLegendOp;
        break;
    }
    case COMMAND_LINE:{
        proc = LineOp;
        break;
    }
    case COMMAND_MARKER:{
        proc = RbcMarkerOp;
        break;
    }
    case COMMAND_PEN:{
        proc = RbcPenOp;
        break;
    }
    case COMMAND_POSTSCRIPT:{
        proc = RbcPostScriptOp;
        break;
    }
    case COMMAND_SNAP:{
        if(objc < 3) {
            Tcl_WrongNumArgs(interp, 2, objv, "?switchse? name");
            return TCL_ERROR;
        }
        proc = SnapOp;
        break;
    }
    case COMMAND_TRANSFORM:{
        if(objc != 4) {
            Tcl_WrongNumArgs(interp, 2, objv, "x y");
            return TCL_ERROR;
        }
        proc = TransformOp;
        break;
    }
    case COMMAND_X2AXIS:{
        proc = X2AxisOp;
        break;
    }
    case COMMAND_XAXIS:{
        proc = XAxisOp;
        break;
    }
    case COMMAND_Y2AXIS:{
        proc = Y2AxisOp;
        break;
    }
    case COMMAND_YAXIS:{
        proc = YAxisOp;
        break;
    }
    default:{
        return TCL_ERROR;
    }
    }
    Tcl_Preserve(graph);
    result = (*proc) (graph, interp, objc, objv);
    Tcl_Release(graph);
    return result;
}

/*
 * GraphMethod_style --
 *
 *  Process -style option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_style(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    const char *chPtr;
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1]))==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

    chPtr = Tcl_GetString(value);
    if(strcmp(chPtr, "line") == 0) {
        graph->classUid = rbcLineElementUid;
        graph->chartStyle = "line";
    } else if(strcmp(chPtr, "bar") == 0) {
        graph->classUid = rbcBarElementUid;
        graph->chartStyle = "bar";
    } else if(strcmp(chPtr, "chart") == 0) {
        graph->classUid = rbcStripElementUid;
        graph->chartStyle = "strip";
    } else {
        Tcl_SetObjResult(interp,
            Tcl_ObjPrintf("wrong -style option, should be line,bar or chart"));
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * GraphMethod_barmode --
 *
 *  Process -barmode option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_barmode(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    const char *string;
    Tcl_Size length;
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1]))==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

    string = Tcl_GetStringFromObj(value, &length);
    if((string[0] == 'n') && (strncmp(string, "normal", length) == 0)) {
        graph->mode = MODE_INFRONT;
    } else if((string[0] == 'i') && (strncmp(string, "infront", length) == 0)) {
        graph->mode = MODE_INFRONT;
    } else if((string[0] == 's') && (strncmp(string, "stacked", length) == 0)) {
        graph->mode = MODE_STACKED;
    } else if((string[0] == 'a') && (strncmp(string, "aligned", length) == 0)) {
        graph->mode = MODE_ALIGNED;
    } else if((string[0] == 'o') && (strncmp(string, "overlap", length) == 0)) {
        graph->mode = MODE_OVERLAP;
    } else {
        Tcl_AppendResult(interp, "bad mode argument \"", string,
            "\": should be \"infront\", \"stacked\", \"overlap\", or \"aligned\"",
            (char *)NULL);
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * GraphMethod_barwidth --
 *
 *  Process -barwidth option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_barwidth(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    double dblVal;
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1]))==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;
    if(Tcl_GetDoubleFromObj(interp, value, &dblVal) != TCL_OK) {
        return TCL_ERROR;
    }
    if(dblVal < 0.1) {
        dblVal = 0.1;
    }
	if (Tko_WidgetOptionSet(widget, objv[objc - 1], Tcl_NewDoubleObj(dblVal)) == NULL) {
		return TCL_ERROR;
	}
    graph->barWidth = dblVal;
    return TCL_OK;
}

/*
 * GraphMethod_plotpadx --
 *
 *  Process -plotpadx option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_plotpadx(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1]))==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

    if(RbcGraphOptionSetPad(interp, graph, value, &graph->padX) != TCL_OK) {
        return TCL_ERROR;
    }
	if (Tko_WidgetOptionSet(widget, objv[objc - 1],
		Tcl_ObjPrintf("%d %d", graph->padX.side1, graph->padX.side2)) == NULL) {
		return TCL_ERROR;
	}
	return TCL_OK;
}

/*
 * GraphMethod_plotpady --
 *
 *  Process -plotpady option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_plotpady(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1])) ==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;
    if(RbcGraphOptionSetPad(interp, graph, value, &graph->padY) != TCL_OK) {
        return TCL_ERROR;
    }
	if (Tko_WidgetOptionSet(widget, objv[objc - 1],
		Tcl_ObjPrintf("%d %d", graph->padY.side1, graph->padY.side2)) == NULL) {
		return TCL_ERROR;
	}
    return TCL_OK;
}

/*
 * GraphMethod_shadow --
 *
 * Process -shadow option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_shadow(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1]))==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;
    if(RbcGraphOptionSetShadow(interp, graph, value,
            &graph->titleTextStyle.shadow) != TCL_OK) {
        return TCL_ERROR;
    }
    if(graph->titleTextStyle.shadow.color != NULL) {
		if (Tko_WidgetOptionSet(widget, objv[objc - 1],
			Tcl_ObjPrintf("%s %d",
				Tk_NameOfColor(graph->titleTextStyle.shadow.color),
				graph->titleTextStyle.shadow.offset)) == NULL) {
			return TCL_ERROR;
		}
    } else {
        Tcl_Obj *tmpPtr;
        tmpPtr = Tcl_NewStringObj("",0);
        Tcl_IncrRefCount(tmpPtr);
		if (Tko_WidgetOptionSet(widget, objv[objc - 1], tmpPtr) == NULL) {
            Tcl_DecrRefCount(tmpPtr);
			return TCL_ERROR;
		}
        Tcl_DecrRefCount(tmpPtr);
    }
    return TCL_OK;
}

/*
 * GraphMethod_tile --
 *
 *  Process -tile option.
 *
 * Results:
 *  TODO
 *
 * Side effects:
 *  TODO
 */
static int
GraphMethod_tile(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_ObjectContext context,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    Tcl_Obj *value;
	Tko_Widget *widget;
    RbcGraph *graph;

	if ((widget = (Tko_Widget *)Tko_WidgetClientData(context)) == NULL
		|| widget->tkWin == NULL
		|| (value = Tko_WidgetOptionGet(widget, objv[objc - 1]))==NULL) {
		return TCL_ERROR;
	}
	graph = (RbcGraph *)widget;

    return (RbcGraphOptionSetTile(interp, graph, value, &graph->tile));
}

/*
 * RbcEventuallyRedrawGraph --
 *
 *      Tells the Tk dispatcher to call the graph display routine at
 *      the next idle point.  This request is made only if the window
 *      is displayed and no other redraw request is pending.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The window is eventually redisplayed.
 */
void
RbcEventuallyRedrawGraph(
    RbcGraph * graph)
{              /* Graph widget record */
	if (graph->widget.tkWin == NULL) {
		return;
	}
    if(!(graph->flags & RBC_REDRAW_PENDING)) {
        Tcl_DoWhenIdle(DisplayGraph, graph);
        graph->flags |= RBC_REDRAW_PENDING;
    }
}

/*
 * GraphEventProc --
 *
 *      This procedure is invoked by the Tk dispatcher for various
 *      events on graphs.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      When the window gets deleted, internal structures get
 *      cleaned up.  When it gets exposed, the graph is eventually
 *      redisplayed.
 */
static void
GraphEventProc(
    ClientData clientData,     /* Graph widget record */
    register XEvent * eventPtr)
{              /* Event which triggered call to routine */
    RbcGraph *graph = clientData;
	if (eventPtr->type == DestroyNotify || graph->widget.tkWin == NULL) {
        return;
	}

    if(eventPtr->type == Expose) {
        if(eventPtr->xexpose.count == 0) {
            graph->flags |= RBC_REDRAW_WORLD;
            RbcEventuallyRedrawGraph(graph);
        }
    } else if((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
        if(eventPtr->xfocus.detail != NotifyInferior) {
            if(eventPtr->type == FocusIn) {
                graph->flags |= RBC_GRAPH_FOCUS;
            } else {
                graph->flags &= ~RBC_GRAPH_FOCUS;
            }
            graph->flags |= RBC_REDRAW_WORLD;
            RbcEventuallyRedrawGraph(graph);
        }
    } else if(eventPtr->type == ConfigureNotify) {
        graph->flags |= (RBC_MAP_WORLD | RBC_REDRAW_WORLD);
        RbcEventuallyRedrawGraph(graph);
    }
}

/*
 * TileChangedProc --
 *
 *      Rebuilds the designated GC with the new tile pixmap.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static void
TileChangedProc(
    ClientData clientData,
    RbcTile tile)
{              /* Not used. */
	RbcGraph *graph = clientData;
	if (graph->widget.tkWin == NULL) {
		return;
	}

    graph->flags |= RBC_REDRAW_WORLD;
    RbcEventuallyRedrawGraph(graph);
}

/*
 * AdjustAxisPointers --
 *
 *      Sets the axis pointers according to whether the axis is
 *      inverted on not.  The axis sites are also reset.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static void
AdjustAxisPointers(
    RbcGraph * graph)
{              /* Graph widget record */
    if(graph->inverted) {
        graph->margins[RBC_MARGIN_LEFT].axes = graph->axisChain[0];
        graph->margins[RBC_MARGIN_BOTTOM].axes = graph->axisChain[1];
        graph->margins[RBC_MARGIN_RIGHT].axes = graph->axisChain[2];
        graph->margins[RBC_MARGIN_TOP].axes = graph->axisChain[3];
    } else {
        graph->margins[RBC_MARGIN_LEFT].axes = graph->axisChain[1];
        graph->margins[RBC_MARGIN_BOTTOM].axes = graph->axisChain[0];
        graph->margins[RBC_MARGIN_RIGHT].axes = graph->axisChain[3];
        graph->margins[RBC_MARGIN_TOP].axes = graph->axisChain[2];
    }
}

/*
 * PickEntry --
 *
 *      Find the closest point from the set of displayed elements,
 *      searching the display list from back to front.  That way, if
 *      the points from two different elements overlay each other exactly,
 *      the one that's on top (visible) is picked.
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static ClientData
PickEntry(
    ClientData clientData,
    int x,
    int y,
    ClientData * contextPtr)
{              /* Not used. */
    RbcGraph *graph = clientData;
    RbcChainLink *linkPtr;
    RbcElement *elemPtr;
    RbcMarker *markerPtr;
    RbcExtents2D exts;

    if(graph->flags & RBC_MAP_ALL) {
        /* Can't pick anything until the next
         * redraw occurs. */
        return NULL;
    }
    RbcGraphExtents(graph, &exts);

    if((x > exts.right) || (x < exts.left) || (y > exts.bottom)
        || (y < exts.top)) {
        /*
         * Sample coordinate is in one of the graph margins.  Can only
         * pick an axis.
         */
        return RbcNearestAxis(graph, x, y);
    }

    /*
     * From top-to-bottom check:
     *  1. markers drawn on top (-under false).
     *  2. elements using its display list back to front.
     *  3. markers drawn under element (-under true).
     */
    markerPtr = (RbcMarker *) RbcNearestMarker(graph, x, y, FALSE);
    if(markerPtr != NULL) {
        /* Found a marker (-under false). */
        return markerPtr;
    }
    {
    RbcClosestSearch search;

        search.along = RBC_SEARCH_BOTH;
        search.halo = graph->halo + 1;
        search.index = -1;
        search.x = x;
        search.y = y;
        search.dist = (double)(search.halo + 1);
        search.mode = RBC_SEARCH_AUTO;

        for(linkPtr = RbcChainLastLink(graph->elements.displayList);
            linkPtr != NULL; linkPtr = RbcChainPrevLink(linkPtr)) {
            elemPtr = RbcChainGetValue(linkPtr);
            if((elemPtr->flags & RBC_MAP_ITEM)
                || (RbcVectorNotifyPending(elemPtr->x.clientId))
                || (RbcVectorNotifyPending(elemPtr->y.clientId))) {
                continue;
            }
            if((!elemPtr->hidden) && (elemPtr->state == RBC_STATE_NORMAL)) {
                (*elemPtr->procsPtr->closestProc) (graph, elemPtr, &search);
            }
        }
        if(search.dist <= (double)search.halo) {
            /* Found an element within the
             * minimum halo distance. */
            return search.elemPtr;
        }
    }
    markerPtr = (RbcMarker *) RbcNearestMarker(graph, x, y, TRUE);
    if(markerPtr != NULL) {
        /* Found a marker (-under true) */
        return markerPtr;
    }
    /* Nothing found. */
    return NULL;
}

/* Widget sub-commands */

/*
 * XAxisOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
XAxisOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    int margin;

    margin = (graph->inverted) ? RBC_MARGIN_LEFT : RBC_MARGIN_BOTTOM;
    return RbcAxisOp(graph, margin, objc, objv);
}

/*
 * X2AxisOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
X2AxisOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    int margin;

    margin = (graph->inverted) ? RBC_MARGIN_RIGHT : RBC_MARGIN_TOP;
    return RbcAxisOp(graph, margin, objc, objv);
}

/*
 * YAxisOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
YAxisOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    int margin;

    margin = (graph->inverted) ? RBC_MARGIN_BOTTOM : RBC_MARGIN_LEFT;
    return RbcAxisOp(graph, margin, objc, objv);
}

/*
 * Y2AxisOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
Y2AxisOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    int margin;

    margin = (graph->inverted) ? RBC_MARGIN_TOP : RBC_MARGIN_RIGHT;
    return RbcAxisOp(graph, margin, objc, objv);
}

/*
 * BarOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
BarOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    return RbcElementOp(graph, interp, objc, objv, rbcBarElementUid);
}

/*
 * LineOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
LineOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    return RbcElementOp(graph, interp, objc, objv, rbcLineElementUid);
}

/*
 * ElementOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
ElementOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    return RbcElementOp(graph, interp, objc, objv, graph->classUid);
}

/*
 * ExtentsOp --
 *
 *      Reports the size of one of several items within the graph.
 *      The following are valid items:
 *
 *        "bottommargin"    Height of the bottom margin
 *        "leftmargin"      Width of the left margin
 *        "legend"          x y w h of the legend
 *        "plotarea"        x y w h of the plotarea
 *        "plotheight"      Height of the plot area
 *        "rightmargin"     Width of the right margin
 *        "topmargin"       Height of the top margin
 *        "plotwidth"       Width of the plot area
 *
 * Results:
 *      Always returns TCL_OK.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
ExtentsOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    char *c;
    Tcl_Size length;
    char string[200];
    if (objc < 3) {
        Tcl_AppendResult(interp, "bad extent item", (char*)NULL);
        return TCL_ERROR;
    }

    c = Tcl_GetStringFromObj(objv[2],&length);
    if((c[0] == 'p') && (length > 4)
        && (strncmp("plotheight", c, length) == 0)) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(graph->bottom - graph->top + 1));
    } else if((c[0] == 'p') && (length > 4)
        && (strncmp("plotwidth", c, length) == 0)) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(graph->right - graph->left + 1));
    } else if((c[0] == 'p') && (length > 4)
        && (strncmp("plotarea", c, length) == 0)) {
        sprintf(string, "%d %d %d %d", graph->left, graph->top,
            graph->right - graph->left + 1, graph->bottom - graph->top + 1);
        Tcl_SetObjResult(interp, Tcl_NewStringObj(string, -1));
    } else if((c[0] == 'l') && (length > 2)
        && (strncmp("legend", c, length) == 0)) {
        sprintf(string, "%d %d %d %d", RbcLegendX(graph->legend),
            RbcLegendY(graph->legend), RbcLegendWidth(graph->legend),
            RbcLegendHeight(graph->legend));
        Tcl_SetObjResult(interp, Tcl_NewStringObj(string, -1));
    } else if((c[0] == 'l') && (length > 2)
        && (strncmp("leftmargin", c, length) == 0)) {
        Tcl_SetObjResult(interp,
            Tcl_NewIntObj(graph->margins[RBC_MARGIN_LEFT].width));
    } else if((c[0] == 'r') && (length > 1)
        && (strncmp("rightmargin", c, length) == 0)) {
        Tcl_SetObjResult(interp,
            Tcl_NewIntObj(graph->margins[RBC_MARGIN_RIGHT].width));
    } else if((c[0] == 't') && (length > 1)
        && (strncmp("topmargin", c, length) == 0)) {
        Tcl_SetObjResult(interp,
            Tcl_NewIntObj(graph->margins[RBC_MARGIN_TOP].height));
    } else if((c[0] == 'b') && (length > 1)
        && (strncmp("bottommargin", c, length) == 0)) {
        Tcl_SetObjResult(interp,
            Tcl_NewIntObj(graph->margins[RBC_MARGIN_BOTTOM].height));
    } else {
        Tcl_AppendResult(interp, "bad extent item \"", Tcl_GetString(objv[2]),
            "\": should be plotheight, plotwidth, leftmargin, rightmargin, \
topmargin, bottommargin, plotarea, or legend", (char *)NULL);
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * InsideOp --
 *
 *      Returns true of false whether the given point is inside
 *      the plotting area (defined by left,bottom right, top).
 *
 * Results:
 *      Always returns TCL_OK.  interp->result will contain
 *      the boolean string representation.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
InsideOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc, /*not used*/
    Tcl_Obj* const objv[])
{
    int x, y;
    RbcExtents2D exts;
    int result;
    if(graph->widget.tkWin == NULL)
        return TCL_ERROR;

    if(Tk_GetPixels(interp, graph->widget.tkWin, Tcl_GetString(objv[2]), &x) != TCL_OK) {
        return TCL_ERROR;
    }
    if(Tk_GetPixels(interp, graph->widget.tkWin, Tcl_GetString(objv[3]), &y) != TCL_OK) {
        return TCL_ERROR;
    }
    RbcGraphExtents(graph, &exts);
    result = RbcPointInRegion(&exts, x, y);
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result));
    return TCL_OK;
}

/*
 * InvtransformOp --
 *
 *      This procedure returns a list of the graph coordinate
 *      values corresponding with the given window X and Y
 *      coordinate positions.
 *
 * Results:
 *      Returns a standard Tcl result.  If an error occurred while
 *      parsing the window positions, TCL_ERROR is returned, and
 *      interp->result will contain the error message.  Otherwise
 *      interp->result will contain a Tcl list of the x and y
 *      coordinates.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
InvtransformOp(
    RbcGraph * graph,          /* Graph widget record */
    Tcl_Interp * interp,
    Tcl_Size objc, /*not used*/
    Tcl_Obj* const objv[])
{
    double x, y;
    RbcPoint2D point;
    RbcAxis2D axes;
    char stringDouble[TCL_DOUBLE_SPACE];

    if(Tcl_ExprDouble(interp, Tcl_GetString(objv[2]), &x) != TCL_OK ||
        Tcl_ExprDouble(interp, Tcl_GetString(objv[3]), &y) != TCL_OK) {
        return TCL_ERROR;
    }
    if(graph->flags & RBC_RESET_AXES) {
        RbcResetAxes(graph);
    }
    /* Perform the reverse transformation, converting from window
     * coordinates to graph data coordinates.  Note that the point is
     * always mapped to the bottom and left axes (which may not be
     * what the user wants).  */

    /*  Pick the first pair of axes */
    axes.x = RbcGetFirstAxis(graph->axisChain[0]);
    axes.y = RbcGetFirstAxis(graph->axisChain[1]);
    point = RbcInvMap2D(graph, x, y, &axes);

    Tcl_PrintDouble(NULL, point.x, stringDouble);
    Tcl_AppendElement(interp, stringDouble);
    Tcl_PrintDouble(NULL, point.y, stringDouble);
    Tcl_AppendElement(interp, stringDouble);
    return TCL_OK;
}

/*
 * TransformOp --
 *
 *      This procedure returns a list of the window coordinates
 *      corresponding with the given graph x and y coordinates.
 *
 * Results:
 *      Returns a standard Tcl result.  interp->result contains
 *      the list of the graph coordinates. If an error occurred
 *      while parsing the window positions, TCL_ERROR is returned,
 *      then interp->result will contain an error message.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
TransformOp(
    RbcGraph * graph,          /* Graph widget record */
    Tcl_Interp * interp,
    Tcl_Size objc,/*not used*/
    Tcl_Obj* const objv[])
{
    double x, y;
    RbcPoint2D point;
    RbcAxis2D axes;

    if((Tcl_ExprDouble(interp, Tcl_GetString(objv[2]), &x) != TCL_OK) ||
        (Tcl_ExprDouble(interp, Tcl_GetString(objv[3]), &y) != TCL_OK)) {
        return TCL_ERROR;
    }
    if(graph->flags & RBC_RESET_AXES) {
        RbcResetAxes(graph);
    }
    /*
     * Perform the transformation from window to graph coordinates.
     * Note that the points are always mapped onto the bottom and left
     * axes (which may not be the what the user wants).
     */
    axes.x = RbcGetFirstAxis(graph->axisChain[0]);
    axes.y = RbcGetFirstAxis(graph->axisChain[1]);

    point = RbcMap2D(graph, x, y, &axes);
    Tcl_AppendPrintfToObj(Tcl_GetObjResult(interp),
        "%d %d", ROUND(point.x), ROUND(point.y));
    return TCL_OK;
}

/*
 * SnapOp --
 *
 *      Snaps a picture of the graph and stores it in the specified image
 *
 * Results:
 *      Returns a standard Tcl result.  interp->result contains
 *      the list of the graph coordinates. If an error occurred
 *      while parsing the window positions, TCL_ERROR is returned,
 *      then interp->result will contain an error message.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static int
SnapOp(
    RbcGraph * graph,          /* Graph widget record */
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    int result;
    Pixmap drawable;
    int noBackingStore = 0;
    Tcl_Size i;
	int width;
	int height;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    /* .g snap ?-width width? ?-height height? name */
	if (objc % 2 != 1) {
		Tcl_AppendResult(interp, "missing name argument: should be \"",
			Tcl_GetString(objv[0]), "snap ?switches? name\"", (char *)NULL);
		return TCL_ERROR;
	}
    height = Tk_Height(tkWin);
    width = Tk_Width(tkWin);
    /* Process switches  */
	for (i = 2; i < objc - 3;) {
		if (strcmp("-width", Tcl_GetString(objv[i])) == 0) {
			if (Tcl_GetIntFromObj(interp, objv[i+1], &width) != TCL_OK || width <0) {
				Tcl_AppendResult(interp, "wrong -width value: ",Tcl_GetString(objv[i+1]),(char *)NULL);
				return TCL_ERROR;
			}
		}
		else if (strcmp("-height",Tcl_GetString(objv[i]))==0) {
			if (Tcl_GetIntFromObj(interp, objv[i+1], &height) != TCL_OK || width <0) {
				Tcl_AppendResult(interp, "wrong -height value: ",Tcl_GetString(objv[i+1]),(char *)NULL);
				return TCL_ERROR;
			}
		}
		else {
			Tcl_AppendResult(interp, "wrong option: ",Tcl_GetString(objv[i]),(char *)NULL);
		}
		i = i + 2;
	}
    if(width < 2) {
        width = 400;
    }
    if(height < 2) {
        height = 400;
    }
    /* Always re-compute the layout of the graph before snapping the photo. */
    graph->width = width;
    graph->height = height;
    RbcLayoutGraph(graph);

    drawable = Tk_WindowId(tkWin);
	/* Create photo image */
    drawable = Tk_GetPixmap(graph->widget.display, drawable, graph->width,
        graph->height, Tk_Depth(tkWin));
	if (drawable == None) {
		Tcl_AppendResult(interp, "no drawable", (char *)NULL);
		return TCL_ERROR;
	}
    graph->flags |= RBC_RESET_WORLD;
    RbcDrawGraph(graph, drawable, noBackingStore);
    result = RbcSnapPhoto(interp, tkWin, drawable, 0, 0,
        width, height, width, height, (char *)Tcl_GetString(objv[objc - 1]), 1.0);
    Tk_FreePixmap(graph->widget.display, drawable);
    graph->flags = RBC_MAP_WORLD;
    RbcEventuallyRedrawGraph(graph);
    return result;
}

static RbcOpSpec graphOps[] = {
    {"axis", 1, (RbcOp) RbcVirtualAxisOp, 2, 0, "oper ?args?",},
    {"bar", 2, (RbcOp) BarOp, 2, 0, "oper ?args?",},
    {"crosshairs", 2, (RbcOp) RbcCrosshairsOp, 2, 0, "oper ?args?",},
    {"element", 2, (RbcOp) ElementOp, 2, 0, "oper ?args?",},
    {"extents", 2, (RbcOp) ExtentsOp, 3, 3, "item",},
    {"grid", 1, (RbcOp) RbcGridOp, 2, 0, "oper ?args?",},
    {"inside", 3, (RbcOp) InsideOp, 4, 4, "winX winY",},
    {"invtransform", 3, (RbcOp) InvtransformOp, 4, 4, "winX winY",},
    {"legend", 2, (RbcOp) RbcLegendOp, 2, 0, "oper ?args?",},
    {"line", 2, (RbcOp) LineOp, 2, 0, "oper ?args?",},
    {"marker", 2, (RbcOp) RbcMarkerOp, 2, 0, "oper ?args?",},
    {"pen", 2, (RbcOp) RbcPenOp, 2, 0, "oper ?args?",},
    {"postscript", 2, (RbcOp) RbcPostScriptOp, 2, 0, "oper ?args?",},
    {"snap", 1, (RbcOp) SnapOp, 3, 0, "?switches? name",},
    {"transform", 1, (RbcOp) TransformOp, 4, 4, "x y",},
    {"x2axis", 2, (RbcOp) X2AxisOp, 2, 0, "oper ?args?",},
    {"xaxis", 2, (RbcOp) XAxisOp, 2, 0, "oper ?args?",},
    {"y2axis", 2, (RbcOp) Y2AxisOp, 2, 0, "oper ?args?",},
    {"yaxis", 2, (RbcOp) YAxisOp, 2, 0, "oper ?args?",},
};

static int nGraphOps = sizeof(graphOps) / sizeof(RbcOpSpec);

/*
 * RbcGraphInstCmdProc --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
int
RbcGraphInstCmdProc(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    RbcOp proc;
    int result;
    RbcGraph *graph = clientData;

    proc = RbcGetOp(interp, nGraphOps, graphOps, RBC_OP_ARG1, objc, objv, 0);
    if(proc == NULL) {
        return TCL_ERROR;
    }
    Tcl_Preserve(graph);
    result = (*proc) (graph, interp, objc, objv);
    Tcl_Release(graph);
    return result;
}

/*
 * DrawMargins --
 *
 *      Draws the exterior region of the graph (axes, ticks, titles, etc)
 *      onto a pixmap. The interior region is defined by the given
 *      rectangle structure.
 *
 *      ---------------------------------
 *          |                               |
 *          |           rectArr[0]          |
 *          |                               |
 *      ---------------------------------
 *          |     |top           right|     |
 *          |     |                   |     |
 *          |     |                   |     |
 *          | [1] |                   | [2] |
 *          |     |                   |     |
 *          |     |                   |     |
 *          |     |                   |     |
 *          |     |                   |     |
 *          |     |                   |     |
 *          |     |left         bottom|     |
 *      ---------------------------------
 *          |                               |
 *          |          rectArr[3]           |
 *          |                               |
 *      ---------------------------------
 *
 *          X coordinate axis
 *          Y coordinate axis
 *          legend
 *          interior border
 *          exterior border
 *          titles (X and Y axis, graph)
 *
 * Returns:
 *      None.
 *
 * Side Effects:
 *      Exterior of graph is displayed in its window.
 */
static void
DrawMargins(
    RbcGraph * graph,
    Drawable drawable /* Pixmap or window to draw into */)
{
    XRectangle rects[4];
	Tk_Window tkWin = graph->widget.tkWin;

	if (tkWin == NULL) {
		return;
	}
    /*
     * Draw the four outer rectangles which encompass the plotting
     * surface. This clears the surrounding area and clips the plot.
     */
    rects[0].x = rects[0].y = rects[3].x = rects[1].x = 0;
    rects[0].width = rects[3].width = (short int)graph->width;
    rects[0].height = (short int)graph->top;
    rects[3].y = graph->bottom;
    rects[3].height = graph->height - graph->bottom;
    rects[2].y = rects[1].y = graph->top;
    rects[1].width = graph->left;
    rects[2].height = rects[1].height = graph->bottom - graph->top;
    rects[2].x = graph->right;
    rects[2].width = graph->width - graph->right;

    if(graph->tile != NULL) {
        RbcSetTileOrigin(tkWin, graph->tile, 0, 0);
        RbcTileRectangles(tkWin, drawable, graph->tile, rects, 4);
    } else {
        XFillRectangles(graph->widget.display, drawable, graph->fillGC, rects, 4);
    }

    /* Draw 3D border around the plotting area */

    if(graph->plotBorderWidth > 0) {
        int x, y, width, height;

        x = graph->left - graph->plotBorderWidth;
        y = graph->top - graph->plotBorderWidth;
        width = (graph->right - graph->left) + (2 * graph->plotBorderWidth);
        height = (graph->bottom - graph->top) + (2 * graph->plotBorderWidth);
        Tk_Draw3DRectangle(tkWin, drawable, graph->border, x, y,
            width, height, graph->plotBorderWidth, graph->plotRelief);
    }
    if(RbcLegendSite(graph->legend) & RBC_LEGEND_IN_MARGIN) {
        /* Legend is drawn on one of the graph margins */
        RbcDrawLegend(graph->legend, drawable);
    }
    if(graph->title != NULL) {
        RbcDrawText(tkWin, drawable, graph->title,
            &graph->titleTextStyle, graph->titleX, graph->titleY);
    }
    RbcDrawAxes(graph, drawable);

}

/*
 * DrawPlotRegion --
 *
 *      Draws the contents of the plotting area.  This consists of
 *      the elements, markers (draw under elements), axis limits,
 *      grid lines, and possibly the legend.  Typically, the output
 *      will be cached into a backing store pixmap, so that redraws
 *      can occur quickly.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static void
DrawPlotRegion(
    RbcGraph * graph,
    Drawable drawable)
{              /* Pixmap or window to draw into */
    /* Clear the background of the plotting area. */
    XFillRectangle(graph->widget.display, drawable, graph->plotFillGC,
        graph->left, graph->top, graph->right - graph->left + 1,
        graph->bottom - graph->top + 1);

    /* Draw the elements, markers, legend, and axis limits. */

    if(!graph->gridPtr->hidden) {
        RbcDrawGrid(graph, drawable);
    }
    RbcDrawMarkers(graph, drawable, RBC_MARKER_UNDER);
    if((RbcLegendSite(graph->legend) & RBC_LEGEND_IN_PLOT) &&
        (!RbcLegendIsRaised(graph->legend))) {
        RbcDrawLegend(graph->legend, drawable);
    }
    RbcDrawAxisLimits(graph, drawable);
    RbcDrawElements(graph, drawable);
}

/*
 * RbcLayoutGraph --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
void
RbcLayoutGraph(
    RbcGraph * graph)
{
    if(graph->flags & RBC_RESET_AXES) {
        RbcResetAxes(graph);
    }
    if(graph->flags & RBC_LAYOUT_NEEDED) {
        RbcLayoutMargins(graph);
        graph->flags &= ~RBC_LAYOUT_NEEDED;
    }
    /* Compute coordinate transformations for graph components */
    if((graph->vRange > 1) && (graph->hRange > 1)) {
        if(graph->flags & RBC_MAP_WORLD) {
            RbcMapAxes(graph);
        }
        RbcMapElements(graph);
        RbcMapMarkers(graph);
        RbcMapGrid(graph);
        graph->flags &= ~(RBC_MAP_ALL);
    }
}

/*
 * RbcDrawGraph --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
void
RbcDrawGraph(
    RbcGraph * graph,
    Drawable drawable,         /* Pixmap or window to draw into */
    int backingStore)
{              /* If non-zero, use backing store for
                * plotting area. */
	Tk_Window tkWin = graph->widget.tkWin;
    if(tkWin == NULL)
        return;
    if(backingStore) {
        /*
         * Create another pixmap to save elements if one doesn't
         * already exist or the size of the window has changed.
         */
        if((graph->backPixmap == None) ||
            (graph->backWidth != graph->width) ||
            (graph->backHeight != graph->height)) {

            if(graph->backPixmap != None) {
                Tk_FreePixmap(graph->widget.display, graph->backPixmap);
            }
            graph->backPixmap = Tk_GetPixmap(graph->widget.display,
                Tk_WindowId(tkWin), graph->width,
                graph->height, Tk_Depth(tkWin));
            graph->backWidth = graph->width;
            graph->backHeight = graph->height;
            graph->flags |= RBC_REDRAW_BACKING_STORE;
        }
        if(graph->flags & RBC_REDRAW_BACKING_STORE) {
            /* The backing store is new or out-of-date. */
            DrawPlotRegion(graph, graph->backPixmap);
            graph->flags &= ~RBC_REDRAW_BACKING_STORE;
        }

        /* Copy the pixmap to the one used for drawing the entire graph. */

        XCopyArea(graph->widget.display, graph->backPixmap, drawable,
            graph->drawGC, graph->left, graph->top,
            (graph->right - graph->left + 1),
            (graph->bottom - graph->top + 1), graph->left, graph->top);
    } else {
        DrawPlotRegion(graph, drawable);
    }

    /* Draw markers above elements */
    RbcDrawMarkers(graph, drawable, RBC_MARKER_ABOVE);
    RbcDrawActiveElements(graph, drawable);

    if(graph->flags & RBC_DRAW_MARGINS) {
        DrawMargins(graph, drawable);
    }
    if((RbcLegendSite(graph->legend) & RBC_LEGEND_IN_PLOT) &&
        (RbcLegendIsRaised(graph->legend))) {
        RbcDrawLegend(graph->legend, drawable);
    }
    /* Draw 3D border just inside of the focus highlight ring. */
    if((graph->borderWidth > 0) && (graph->relief != TK_RELIEF_FLAT)) {
        Tk_Draw3DRectangle(tkWin, drawable, graph->border,
            graph->highlightWidth, graph->highlightWidth,
            graph->width - 2 * graph->highlightWidth,
            graph->height - 2 * graph->highlightWidth,
            graph->borderWidth, graph->relief);
    }
    /* Draw focus highlight ring. */
    if((graph->highlightWidth > 0) && (graph->flags & RBC_GRAPH_FOCUS)) {
    GC  gc;

        gc = Tk_GCForColor(graph->highlightColor, drawable);
        Tk_DrawFocusHighlight(tkWin, gc, graph->highlightWidth,
            drawable);
    }
}

/*
 * UpdateMarginTraces --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
static void
UpdateMarginTraces(
    RbcGraph * graph)
{
    RbcMargin *marginPtr;
    int size;
    register int i;

    for(i = 0; i < 4; i++) {
        marginPtr = graph->margins + i;
        if(marginPtr->varName != NULL) {        /* Trigger variable traces */
            if((marginPtr->site == RBC_MARGIN_LEFT) ||
                (marginPtr->site == RBC_MARGIN_RIGHT)) {
                size = marginPtr->width;
            } else {
                size = marginPtr->height;
            }
            Tcl_SetVar2Ex(graph->widget.interp, marginPtr->varName, NULL,
                Tcl_NewIntObj(size), TCL_GLOBAL_ONLY);
        }
    }
}

/*
 * DisplayGraph --
 *
 *      This procedure is invoked to display a graph widget.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Commands are output to X to display the graph in its
 *      current mode.
 */
static void
DisplayGraph(
    ClientData clientData)
{
    RbcGraph *graph = clientData;
    Pixmap drawable;
	Tk_Window tkWin = graph->widget.tkWin;
    graph->flags &= ~RBC_REDRAW_PENDING;
	if (tkWin == NULL) {
		return;
	}

    if(RbcGraphUpdateNeeded(graph)) {
        /*
         * One of the elements of the graph has a vector notification
         * pending.  This means that the vector will eventually notify
         * the graph that its data has changed.  Since the graph uses
         * the actual vector (not a copy) we need to keep in-sync.
         * Therefore don't draw right now but wait until we've been
         * notified before redrawing.
         */
        return;
    }
    graph->width = Tk_Width(tkWin);
    graph->height = Tk_Height(tkWin);
    RbcLayoutGraph(graph);
    RbcUpdateCrosshairs(graph);
    if(!Tk_IsMapped(tkWin)) {
        /* The graph's window isn't displayed, so don't bother
         * drawing anything.  By getting this far, we've at least
         * computed the coordinates of the graph's new layout.  */
        return;
    }

    /* Disable crosshairs before redisplaying to the screen */
    RbcDisableCrosshairs(graph);
    /*
     * Create a pixmap the size of the window for double buffering.
     */
    if(graph->doubleBuffer) {
        drawable = Tk_GetPixmap(graph->widget.display, Tk_WindowId(tkWin),
            graph->width, graph->height, Tk_Depth(tkWin));
    } else {
        drawable = Tk_WindowId(tkWin);
    }
	if (drawable == None) {
		return; /* should not happen */
	}
    RbcDrawGraph(graph, drawable, graph->backingStore && graph->doubleBuffer);
    if(graph->flags & RBC_DRAW_MARGINS) {
        XCopyArea(graph->widget.display, drawable, Tk_WindowId(tkWin),
            graph->drawGC, 0, 0, graph->width, graph->height, 0, 0);
    } else {
        XCopyArea(graph->widget.display, drawable, Tk_WindowId(tkWin),
            graph->drawGC, graph->left, graph->top,
            (graph->right - graph->left + 1),
            (graph->bottom - graph->top + 1), graph->left, graph->top);
    }
    if(graph->doubleBuffer) {
        Tk_FreePixmap(graph->widget.display, drawable);
    }
    RbcEnableCrosshairs(graph);
    graph->flags &= ~RBC_RESET_WORLD;
    UpdateMarginTraces(graph);
}

/*
 * RbcGetGraphFromWindowData --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
RbcGraph *
RbcGetGraphFromWindowData(
    Tk_Window tkwin)
{
RbcGraph *graph;

    while(tkwin != NULL) {
        graph = (RbcGraph *) ((TkWindow *)tkwin)->instanceData;
        if(graph != NULL) {
            return graph;
        }
        tkwin = Tk_Parent(tkwin);
    }
    return NULL;
}

/*
 * RbcGraphType --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 */
int
RbcGraphType(
    RbcGraph * graph)
{
    if(graph->classUid == rbcLineElementUid) {
        return RBC_GRAPH;
    } else if(graph->classUid == rbcBarElementUid) {
        return RBC_BARCHART;
    } else if(graph->classUid == rbcStripElementUid) {
        return RBC_STRIPCHART;
    }
    return 0;
}

/* vim: set ts=4 sw=4 sts=4 ff=unix et : */
