Graphs now have a legend.

This commit is contained in:
Martin Preuss
2025-09-29 16:11:53 +02:00
parent 3142e886b4
commit 271372969d
14 changed files with 443 additions and 10 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*~ *~
build/ build/
core

6
0BUILD
View File

@@ -150,10 +150,16 @@
<define name="OS_TYPE" value="$(GWBUILD_SYSTEM)" quoted="TRUE" /> <define name="OS_TYPE" value="$(GWBUILD_SYSTEM)" quoted="TRUE" />
<define name="OS_SHORTNAME" value="$(GWBUILD_SYSTEM)" quoted="TRUE" /> <define name="OS_SHORTNAME" value="$(GWBUILD_SYSTEM)" quoted="TRUE" />
<data dist="TRUE" install="$(libdir)/pkgconfig" generated="true">
aqdiagram.pc
</data>
<subdirs> <subdirs>
src src
</subdirs> </subdirs>
<writeFile name="aqdiagram.pc" />
<!-- Build with "gwbuild -B gettext-src" --> <!-- Build with "gwbuild -B gettext-src" -->
<ifVarMatches name="xgettext_EXISTS" value="TRUE" > <ifVarMatches name="xgettext_EXISTS" value="TRUE" >

14
aqdiagram.pc.in Normal file
View File

@@ -0,0 +1,14 @@
prefix=@prefix@
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: aqdiagram
Description: AqDiagram is a library for creating simple diagrams.
Version: @VERSION@
Libs: -L${libdir} -laqdiagram
Cflags: -I${includedir}/aqdiagram
aqdatabase_typemakerdir=${prefix}/share/aqdiagram/typemaker2

View File

@@ -59,7 +59,7 @@
</setVar> </setVar>
<headers dist="false" install="$(pkgincludedir)"> <headers dist="false" install="$(pkgincludedir)/draw">
$(local/built_headers_pub) $(local/built_headers_pub)
</headers> </headers>
@@ -68,7 +68,7 @@
</headers> </headers>
<headers dist="true" install="$(pkgincludedir)"> <headers dist="true" install="$(pkgincludedir)/draw">
context_cairo.h context_cairo.h
w_drawable.h w_drawable.h
</headers> </headers>

View File

@@ -67,7 +67,7 @@
<headers dist="false" install="$(pkgincludedir)"> <headers dist="false" install="$(pkgincludedir)/graph">
$(local/built_headers_pub) $(local/built_headers_pub)
</headers> </headers>
@@ -76,7 +76,7 @@
</headers> </headers>
<headers dist="true" install="$(pkgincludedir)"> <headers dist="true" install="$(pkgincludedir)/graph">
graph.h graph.h
timegraph.h timegraph.h
w_graph.h w_graph.h
@@ -84,6 +84,7 @@
w_xaxis.h w_xaxis.h
w_yaxis.h w_yaxis.h
w_viewport.h w_viewport.h
w_legend.h
</headers> </headers>
@@ -92,6 +93,7 @@
w_graph_p.h w_graph_p.h
w_axis_p.h w_axis_p.h
w_viewport_p.h w_viewport_p.h
w_legend_p.h
</headers> </headers>
@@ -104,6 +106,7 @@
w_xaxis.c w_xaxis.c
w_yaxis.c w_yaxis.c
w_viewport.c w_viewport.c
w_legend.c
</sources> </sources>

View File

@@ -0,0 +1,156 @@
/****************************************************************************
* This file is part of the project AqDiagram.
* AqDiagram (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./timegraph.h"
#include <gwenhywfar/debug.h>
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static void _setupTicksForTimeAxis(AQDG_GRAPH_AXIS *axis);
static void _setupTicksForDataAxis(AQDG_GRAPH_AXIS *axis);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
AQDG_GRAPH *AQDG_TimeGraph_new(const char *sTitle,
const char *sSubTitle,
const char *sYLabel,
const char *sYUnits,
int yPrecision)
{
AQDG_GRAPH *g;
AQDG_GRAPH_AXIS *xAxis;
AQDG_GRAPH_AXIS *yAxis;
AQDG_GRAPH_SUBGRAPH *subGraph;
g=AQDG_Graph_new();
AQDG_Graph_SetTitle(g, sTitle);
AQDG_Graph_SetSubTitle(g, sSubTitle);
xAxis=AQDG_Graph_Axis_new();
AQDG_Graph_Axis_SetLabel(xAxis, "Time");
AQDG_Graph_Axis_SetPrecision(xAxis, 0);
AQDG_Graph_SetAxis(g, AQDG_GRAPH_AXISPOS_BOTTOM, xAxis);
yAxis=AQDG_Graph_Axis_new();
AQDG_Graph_Axis_SetLabel(yAxis, sYLabel);
AQDG_Graph_Axis_SetPrecision(yAxis, yPrecision);
AQDG_Graph_Axis_SetUnits(yAxis, sYUnits);
AQDG_Graph_SetAxis(g, AQDG_GRAPH_AXISPOS_LEFT, yAxis);
subGraph=AQDG_Graph_SubGraph_new();
AQDG_Graph_SubGraph_SetIndexAxisX(subGraph, AQDG_GRAPH_AXISPOS_BOTTOM);
AQDG_Graph_SubGraph_SetIndexAxisY(subGraph, AQDG_GRAPH_AXISPOS_LEFT);
AQDG_Graph_AddSubGraph(g, subGraph);
return g;
}
void AQDG_TimeGraph_AddCurve(AQDG_GRAPH *g, const char *sLabel, int graphType, AQDG_GRAPH_DATAPAIR_LIST *dpList)
{
AQDG_GRAPH_SUBGRAPH *subGraph;
subGraph=AQDG_Graph_GetFirstSubGraph(g);
if (subGraph) {
AQDG_GRAPH_CURVE *curve;
curve=AQDG_Graph_Curve_new();
AQDG_Graph_Curve_SetLabel(curve, sLabel);
AQDG_Graph_Curve_SetGraphType(curve, graphType);
AQDG_Graph_Curve_SetDataPairs(curve, dpList);
AQDG_Graph_SubGraph_AddCurve(subGraph, curve);
}
}
void AQDG_TimeGraph_SetupTicks(AQDG_GRAPH *g)
{
AQDG_GRAPH_AXIS *axis;
AQDG_Graph_CalcMinMaxValues(g);
/* create ticks for X axis */
axis=AQDG_Graph_GetAxisByIndex(g, AQDG_GRAPH_AXISPOS_BOTTOM);
if (axis) {
_setupTicksForTimeAxis(axis);
}
/* create ticks for Y axis */
axis=AQDG_Graph_GetAxisByIndex(g, AQDG_GRAPH_AXISPOS_LEFT);
if (axis) {
_setupTicksForDataAxis(axis);
}
}
void _setupTicksForTimeAxis(AQDG_GRAPH_AXIS *axis)
{
double minValue;
double maxValue;
double diffInDays;
minValue=AQDG_Graph_Axis_GetMinValue(axis);
maxValue=AQDG_Graph_Axis_GetMaxValue(axis);
diffInDays=(maxValue-minValue)/(24*60*60);
DBG_ERROR(NULL, "Difference in days: %f.0", diffInDays);
if (diffInDays<3) {
DBG_ERROR(NULL, "Gen hour ticks");
AQDG_Graph_Axis_GenHourTicks(axis, 0);
}
else if (diffInDays<32) {
DBG_ERROR(NULL, "Gen day ticks");
AQDG_Graph_Axis_GenDayTicks(axis, 0);
}
else if (diffInDays<90) {
DBG_ERROR(NULL, "Gen week and day ticks");
AQDG_Graph_Axis_GenWeekTicks(axis, 0);
AQDG_Graph_Axis_GenDayTicks(axis, 1);
}
else {
DBG_ERROR(NULL, "Gen month ticks");
AQDG_Graph_Axis_GenMonthTicks(axis, 0);
if (diffInDays<100)
AQDG_Graph_Axis_GenDayTicks(axis, 1);
else if (diffInDays<400)
AQDG_Graph_Axis_GenWeekTicks(axis, 1);
}
}
void _setupTicksForDataAxis(AQDG_GRAPH_AXIS *axis)
{
AQDG_Graph_Axis_GenLog10Ticks(axis);
}

View File

@@ -0,0 +1,29 @@
/****************************************************************************
* This file is part of the project AqDiagram.
* AqDiagram (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQDG_GRAPH_TIMEGRAPH_H
#define AQDG_GRAPH_TIMEGRAPH_H
#include <aqdiagram/placement/object.h>
#include <aqdiagram/graph/graph.h>
AQDG_API AQDG_GRAPH *AQDG_TimeGraph_new(const char *sTitle,
const char *sSubTitle,
const char *sYLabel,
const char *sYUnits,
int yPrecision);
AQDG_API void AQDG_TimeGraph_AddCurve(AQDG_GRAPH *g, const char *sLabel, int graphType, AQDG_GRAPH_DATAPAIR_LIST *dpList);
AQDG_API void AQDG_TimeGraph_SetupTicks(AQDG_GRAPH *g);
#endif

View File

@@ -15,6 +15,7 @@
#include "aqdiagram/graph/w_xaxis.h" #include "aqdiagram/graph/w_xaxis.h"
#include "aqdiagram/graph/w_yaxis.h" #include "aqdiagram/graph/w_yaxis.h"
#include "aqdiagram/graph/w_viewport.h" #include "aqdiagram/graph/w_viewport.h"
#include "aqdiagram/graph/w_legend.h"
#include "aqdiagram/draw/w_vlayout.h" #include "aqdiagram/draw/w_vlayout.h"
#include "aqdiagram/draw/w_mlayout.h" #include "aqdiagram/draw/w_mlayout.h"
#include "aqdiagram/draw/w_label.h" #include "aqdiagram/draw/w_label.h"
@@ -113,6 +114,9 @@ void _setupObjectTree(AQDG_OBJECT *o)
const char *s; const char *s;
xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_GRAPH, o); xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_GRAPH, o);
AQDG_DrawableWidget_SetForegroundPenId(o, xo->penArray[AQDG_GRAPHWIDGET_PEN_IDX_TITLE]);
AQDG_DrawableWidget_SetBackgroundPenId(o, xo->penArray[AQDG_GRAPHWIDGET_PEN_IDX_BACKGROUND]);
AQDG_DrawableWidget_SetFontId(o, xo->fontArray[AQDG_GRAPHWIDGET_FONT_IDX_SUBTITLE]);
s=AQDG_Graph_GetTitle(xo->graph); s=AQDG_Graph_GetTitle(xo->graph);
if (s) if (s)
@@ -136,8 +140,7 @@ void _setupObjectTree(AQDG_OBJECT *o)
_setupMatrix(o); _setupMatrix(o);
/* TODO: add label for legend */ AQDG_LegendWidget_new(o, AQDG_OBJECT_OPTIONS_STRETCHX, o);
} }

View File

@@ -0,0 +1,175 @@
/****************************************************************************
* This file is part of the project AqDiagram.
* AqDiagram (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "./w_legend_p.h"
#include "aqdiagram/draw/w_drawable.h"
#include "aqdiagram/draw/w_mlayout.h"
#include "aqdiagram/draw/w_label.h"
#include <gwenhywfar/debug.h>
/* ------------------------------------------------------------------------------------------------
* forward declarations
* ------------------------------------------------------------------------------------------------
*/
static GWENHYWFAR_CB void _freeData(void *bp, void *p);
static void _setupWidget(AQDG_OBJECT *o, AQDG_WIDGET_LEGEND *xo);
static void _addCurve(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *drawContext, const AQDG_GRAPH_CURVE *curve, int pen);
/* ------------------------------------------------------------------------------------------------
* code
* ------------------------------------------------------------------------------------------------
*/
GWEN_INHERIT(AQDG_OBJECT, AQDG_WIDGET_LEGEND);
AQDG_OBJECT *AQDG_LegendWidget_new(AQDG_OBJECT *parent, uint32_t options, AQDG_OBJECT *graphWidget)
{
AQDG_OBJECT *o;
AQDG_WIDGET_LEGEND *xo;
o=AQDG_MatrixLayoutWidgetByRows_new(parent, options, AQDG_DrawableWidget_GetDrawContext(graphWidget), 5);
AQDG_Object_SetName(o, "LegendWidget");
GWEN_NEW_OBJECT(AQDG_WIDGET_LEGEND, xo);
GWEN_INHERIT_SETDATA(AQDG_OBJECT, AQDG_WIDGET_LEGEND, o, xo, _freeData);
xo->graphWidget=graphWidget;
_setupWidget(o, xo);
return o;
}
GWENHYWFAR_CB void _freeData(void *bp, void *p)
{
AQDG_WIDGET_LEGEND *xo;
xo=(AQDG_WIDGET_LEGEND*) p;
GWEN_FREE_OBJECT(xo);
}
void _setupWidget(AQDG_OBJECT *o, AQDG_WIDGET_LEGEND *xo)
{
AQDG_DRAW_CONTEXT *dc;
AQDG_GRAPH *graph;
const AQDG_GRAPH_SUBGRAPH_LIST *subGraphList;
const AQDG_GRAPH_SUBGRAPH *subGraph;
int penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;
dc=AQDG_DrawableWidget_GetDrawContext(o);
AQDG_DrawableWidget_SetFontId(o, AQDG_GraphWidget_GetFont(xo->graphWidget, AQDG_GRAPHWIDGET_FONT_IDX_SUBTITLE));
AQDG_DrawableWidget_new(o, 0, dc);
AQDG_LabelWidget_new(o, 0, dc, "Cur:");
AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNCENTER | AQDG_DRAWABLE_OPTIONS_JUSTIFY_HCENTER, dc, "Min:");
AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNCENTER | AQDG_DRAWABLE_OPTIONS_JUSTIFY_HCENTER, dc, "Avg:");
AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNCENTER | AQDG_DRAWABLE_OPTIONS_JUSTIFY_HCENTER, dc, "Max:");
graph=AQDG_GraphWidget_GetGraph(xo->graphWidget);
subGraphList=AQDG_Graph_GetSubGraphList(graph);
subGraph=subGraphList?AQDG_Graph_SubGraph_List_First(subGraphList):NULL;
while (subGraph) {
const AQDG_GRAPH_CURVE_LIST *curveList;
const AQDG_GRAPH_CURVE *curve;
curveList=AQDG_Graph_SubGraph_GetCurves(subGraph);
curve=curveList?AQDG_Graph_Curve_List_First(curveList):NULL;
while(curve) {
int pen;
pen=AQDG_GraphWidget_GetPen(xo->graphWidget, penIdx);
_addCurve(o, dc, curve, pen);
penIdx++;
if (penIdx>=AQDG_GRAPHWIDGET_PEN_IDX_LAST)
penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;
curve=AQDG_Graph_Curve_List_Next(curve);
}
subGraph=AQDG_Graph_SubGraph_List_Next(subGraph);
}
}
void _addCurve(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc, const AQDG_GRAPH_CURVE *curve, int labelPen)
{
const AQDG_GRAPH_DATAPAIR_LIST *dpList;
dpList=AQDG_Graph_Curve_GetDataPairs(curve);
if (dpList && AQDG_Graph_DataPair_List_GetCount(dpList)) {
GWEN_BUFFER *dbuf;
const AQDG_GRAPH_DATAPAIR *dp;
AQDG_OBJECT *label;
const char *s;
double v;
dbuf=GWEN_Buffer_new(0, 256, 0, 1);
/* curve label */
s=AQDG_Graph_Curve_GetLabel(curve);
label=AQDG_LabelWidget_new(o, 0, dc, (s && *s)?s:"no label");
AQDG_Object_SetName(label, "CurveLabel");
AQDG_DrawableWidget_SetForegroundPenId(label, labelPen); /* color label */
/* last value */
dp=AQDG_Graph_DataPair_List_Last(dpList);
v=AQDG_Graph_DataPair_GetValueY(dp);
GWEN_Buffer_AppendArgs(dbuf, "%.2f", v);
label=AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNRIGHT | AQDG_DRAWABLE_OPTIONS_JUSTIFY_RIGHT, dc, GWEN_Buffer_GetStart(dbuf));
AQDG_Object_SetName(label, "LastValueLabel");
GWEN_Buffer_Reset(dbuf);
/* min value */
GWEN_Buffer_AppendArgs(dbuf, "%.2f", AQDG_Graph_Curve_GetMinValueY(curve));
label=AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNRIGHT | AQDG_DRAWABLE_OPTIONS_JUSTIFY_RIGHT, dc, GWEN_Buffer_GetStart(dbuf));
AQDG_Object_SetName(label, "MinValueLabel");
GWEN_Buffer_Reset(dbuf);
/* calculate average */
dp=AQDG_Graph_DataPair_List_First(dpList);
v=0.0;
while(dp) {
v+=AQDG_Graph_DataPair_GetValueY(dp);
dp=AQDG_Graph_DataPair_List_Next(dp);
}
v/=(double) AQDG_Graph_DataPair_List_GetCount(dpList);
GWEN_Buffer_AppendArgs(dbuf, "%.2f", v);
label=AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNRIGHT | AQDG_DRAWABLE_OPTIONS_JUSTIFY_RIGHT, dc, GWEN_Buffer_GetStart(dbuf));
AQDG_Object_SetName(label, "AvgValueLabel");
GWEN_Buffer_Reset(dbuf);
/* max value */
GWEN_Buffer_AppendArgs(dbuf, "%.2f", AQDG_Graph_Curve_GetMaxValueY(curve));
label=AQDG_LabelWidget_new(o, AQDG_OBJECT_OPTIONS_HALIGNRIGHT | AQDG_DRAWABLE_OPTIONS_JUSTIFY_RIGHT, dc, GWEN_Buffer_GetStart(dbuf));
AQDG_Object_SetName(label, "MaxValueLabel");
GWEN_Buffer_Reset(dbuf);
GWEN_Buffer_free(dbuf);
}
}

View File

@@ -0,0 +1,21 @@
/****************************************************************************
* This file is part of the project AqDiagram.
* AqDiagram (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQDG_GRAPH_W_LEGEND_H
#define AQDG_GRAPH_W_LEGEND_H
#include <aqdiagram/graph/w_graph.h>
AQDG_API AQDG_OBJECT *AQDG_LegendWidget_new(AQDG_OBJECT *parent, uint32_t options, AQDG_OBJECT *graphWidget);
#endif

View File

@@ -0,0 +1,23 @@
/****************************************************************************
* This file is part of the project AqDiagram.
* AqDiagram (c) by 2025 Martin Preuss, all rights reserved.
*
* The license for this file can be found in the file COPYING which you
* should have received along with this file.
****************************************************************************/
#ifndef AQDG_GRAPH_W_LEGEND_P_H
#define AQDG_GRAPH_W_LEGEND_P_H
#include <aqdiagram/graph/w_legend.h>
typedef struct AQDG_WIDGET_LEGEND AQDG_WIDGET_LEGEND;
struct AQDG_WIDGET_LEGEND {
AQDG_OBJECT *graphWidget;
};
#endif

View File

@@ -121,6 +121,7 @@ void _drawCurves(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc)
AQDG_GRAPH *graph; AQDG_GRAPH *graph;
const AQDG_GRAPH_SUBGRAPH_LIST *subGraphList; const AQDG_GRAPH_SUBGRAPH_LIST *subGraphList;
const AQDG_GRAPH_SUBGRAPH *subGraph; const AQDG_GRAPH_SUBGRAPH *subGraph;
int penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;
xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_VIEWPORT, o); xo=GWEN_INHERIT_GETDATA(AQDG_OBJECT, AQDG_WIDGET_VIEWPORT, o);
graph=AQDG_GraphWidget_GetGraph(xo->graphObject); graph=AQDG_GraphWidget_GetGraph(xo->graphObject);
@@ -132,7 +133,6 @@ void _drawCurves(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc)
double maxValueX; double maxValueX;
double minValueY; double minValueY;
double maxValueY; double maxValueY;
int penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;
const AQDG_GRAPH_CURVE_LIST *curveList; const AQDG_GRAPH_CURVE_LIST *curveList;
const AQDG_GRAPH_CURVE *curve; const AQDG_GRAPH_CURVE *curve;
@@ -150,6 +150,8 @@ void _drawCurves(AQDG_OBJECT *o, AQDG_DRAW_CONTEXT *dc)
_drawCurve(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen); _drawCurve(o, dc, curve, minValueX, maxValueX, minValueY, maxValueY, pen);
penIdx++; penIdx++;
if (penIdx>=AQDG_GRAPHWIDGET_PEN_IDX_LAST)
penIdx=AQDG_GRAPHWIDGET_PEN_IDX_CURVE0;
curve=AQDG_Graph_Curve_List_Next(curve); curve=AQDG_Graph_Curve_List_Next(curve);
} }

View File

@@ -177,10 +177,10 @@ int test4()
g=AQDG_TimeGraph_new("Title", "Subtitle", "Value", "mm", 0); g=AQDG_TimeGraph_new("Title", "Subtitle", "Value", "mm", 0);
dpList=_mkTestData1(); dpList=_mkTestData1();
AQDG_TimeGraph_AddCurve(g, "Testdata", AQDG_GRAPH_TYPE_LINE, dpList); AQDG_TimeGraph_AddCurve(g, "Testdata1", AQDG_GRAPH_TYPE_LINE, dpList);
dpList=_mkTestData2(); dpList=_mkTestData2();
AQDG_TimeGraph_AddCurve(g, "Testdata", AQDG_GRAPH_TYPE_POINTS, dpList); AQDG_TimeGraph_AddCurve(g, "Testdata2", AQDG_GRAPH_TYPE_POINTS, dpList);
AQDG_TimeGraph_SetupTicks(g); AQDG_TimeGraph_SetupTicks(g);

View File

@@ -60,7 +60,7 @@
</headers> </headers>
<headers dist="true" install="$(pkgincludedir)"> <headers dist="true" install="$(pkgincludedir)/placement">
layout.h layout.h
o_layout.h o_layout.h
o_hlayout.h o_hlayout.h