From 3307ab89fc2a8297783d2d9d6dc71c69a727644e Mon Sep 17 00:00:00 2001
From: Tom Greig <tag2y19@soton.ac.uk>
Date: Tue, 2 Jul 2024 12:20:39 +0100
Subject: [PATCH] Add log scale options

--xlog, --ylog and --yrlog.  pretty() needed tweaking to handle it and
some of the position calculations have been moved around a bit to make
things neater.
---
 src/main.c | 226 +++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 176 insertions(+), 50 deletions(-)

diff --git a/src/main.c b/src/main.c
index cf4b87a..eab74d8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -93,6 +93,10 @@ struct context {
 		} max, min;
 	} yrange, yrrange;
 
+	bool xlog;
+	bool ylog;
+	bool yrlog;
+
 	PangoFontDescription* tick_font_description;
 
 	double margins[4];
@@ -167,7 +171,7 @@ static void parse_headers( struct context* context );
 static void parse_data( struct context* context );
 static void plot_draw( GtkDrawingArea* plot,
 	cairo_t* cr, int width, int height, void* data );
-static int pretty( double* lo, double* up, int ndiv );
+static int pretty( double* lo, double* up, int ndiv, bool log );
 
 /* PUBLIC FUNK!!! */
 
@@ -236,6 +240,27 @@ int main( int argc, char** argv ) {
 			.arg_data = &( context->yrrange.arg ),
 			.description = "Right y-axis range [min]:[max]",
 			.arg_description = "range",
+		}, {
+			.long_name = "xlog",
+			.short_name = '\0',
+			.flags = G_OPTION_FLAG_IN_MAIN,
+			.arg = G_OPTION_ARG_NONE,
+			.arg_data = &( context->xlog ),
+			.description = "Use log scale for the x axis",
+		}, {
+			.long_name = "ylog",
+			.short_name = '\0',
+			.flags = G_OPTION_FLAG_IN_MAIN,
+			.arg = G_OPTION_ARG_NONE,
+			.arg_data = &( context->ylog ),
+			.description = "Use log scale for the y axis",
+		}, {
+			.long_name = "yrlog",
+			.short_name = '\0',
+			.flags = G_OPTION_FLAG_IN_MAIN,
+			.arg = G_OPTION_ARG_NONE,
+			.arg_data = &( context->yrlog ),
+			.description = "Use log scale for the right y axis",
 		}, {
 			.long_name = "font",
 			.short_name = 'f',
@@ -1645,11 +1670,14 @@ static void plot_draw( GtkDrawingArea* plot,
 		yrrange[1] = context->yrrange.max.value;
 	}
 
-	int xdivs = pretty( &( xrange[0] ), &( xrange[1] ), 10 );
+	int xdivs = pretty(
+		&( xrange[0] ), &( xrange[1] ), 10, context->xlog );
 	int yldivs = lset ?
-		pretty( &( ylrange[0] ), &( ylrange[1] ), 10 ) : 0;
+		pretty(
+			&( ylrange[0] ), &( ylrange[1] ), 10, context->ylog ) : 0;
 	int yrdivs = rset ?
-		pretty( &( yrrange[0] ), &( yrrange[1] ), 10 ) : 0;
+		pretty(
+			&( yrrange[0] ), &( yrrange[1] ), 10, context->yrlog ) : 0;
 
 	char tick_buffer[512];
 
@@ -1672,15 +1700,21 @@ static void plot_draw( GtkDrawingArea* plot,
 				context->xtick_layouts.layouts[i],
 				context->tick_font_description );
 		}
-		if ( context->time ) {
-			time_t t = xrange[0] + ( (double) i ) / ( xdivs - 1 ) *
+		double x;
+		if ( context->xlog ) {
+			x = pow( 10.0, log10( xrange[0] ) +
+				( (double) i ) / ( xdivs - 1 ) *
+				log10( xrange[1] / xrange[0] ) );
+		} else {
+			x = xrange[0] + ( (double) i ) / ( xdivs - 1 ) *
 				( xrange[1] - xrange[0] );
+		}
+		if ( context->time ) {
+			time_t t = (time_t) x;
 			struct tm* tm = localtime( &t );
 			strftime( tick_buffer, 512, "%H:%M:%S", tm );
 		} else {
-			sprintf( tick_buffer, "%.3g",
-				xrange[0] + ( (double) i ) / ( xdivs - 1 ) *
-					( xrange[1] - xrange[0] ) );
+			sprintf( tick_buffer, "%.3g", x );
 		}
 		pango_layout_set_text(
 			context->xtick_layouts.layouts[i], tick_buffer, -1 );
@@ -1712,8 +1746,16 @@ static void plot_draw( GtkDrawingArea* plot,
 				context->yltick_layouts.layouts[i],
 				context->tick_font_description );
 		}
-		sprintf( tick_buffer, "%.3g", ylrange[0] +
-			( (double) i ) / ( yldivs - 1 ) * ( ylrange[1] - ylrange[0] ) );
+		double y;
+		if ( context->ylog ) {
+			y = pow( 10.0, log10( ylrange[0] ) +
+				( (double) i ) / ( yldivs - 1 ) *
+				log10( ylrange[1] / ylrange[0] ) );
+		} else {
+			y = ylrange[0] + ( (double) i ) / ( yldivs - 1 ) *
+				( ylrange[1] - ylrange[0] );
+		}
+		sprintf( tick_buffer, "%.3g", y );
 		pango_layout_set_text(
 			context->yltick_layouts.layouts[i], tick_buffer, -1 );
 		int tick_size;
@@ -1744,8 +1786,16 @@ static void plot_draw( GtkDrawingArea* plot,
 				context->yrtick_layouts.layouts[i],
 				context->tick_font_description );
 		}
-		sprintf( tick_buffer, "%.3g", yrrange[0] +
-			( (double) i ) / ( yrdivs - 1 ) * ( yrrange[1] - yrrange[0] ) );
+		double y;
+		if ( context->yrlog ) {
+			y = pow( 10.0, log10( yrrange[0] ) +
+				( (double) i ) / ( yrdivs - 1 ) *
+				log10( yrrange[1] / yrrange[0] ) );
+		} else {
+			y = yrrange[0] + ( (double) i ) / ( yrdivs - 1 ) *
+				( yrrange[1] - yrrange[0] );
+		}
+		sprintf( tick_buffer, "%.3g", y );
 		pango_layout_set_text(
 			context->yrtick_layouts.layouts[i], tick_buffer, -1 );
 		int tick_size;
@@ -1793,10 +1843,21 @@ static void plot_draw( GtkDrawingArea* plot,
 			double* yrange = context->data->y[i].axis == LEFT ?
 				ylrange : yrrange;
 
-			cairo_rectangle( cr, width / 2 - 1,
-				height - 5 - ( height - 10 ) *
+			double y;
+			if ( ( context->data->y[i].axis == LEFT &&
+						context->ylog ) ||
+					( context->data->y[i].axis == RIGHT &&
+						context->yrlog ) ) {
+				y = height - 5 - ( height - 10 ) *
+					log10( context->data->y[i].v / yrange[0] ) /
+					log10( yrange[1] / yrange[0] );
+			} else {
+				y = height - 5 - ( height - 10 ) *
 					( context->data->y[i].v - yrange[0] ) /
-					( yrange[1] - yrange[0] ) - 1, 2, 2 );
+					( yrange[1] - yrange[0] );
+			}
+
+			cairo_rectangle( cr, width / 2 - 1, y - 1, 2, 2 );
 			cairo_fill( cr );
 
 		}
@@ -1832,16 +1893,39 @@ static void plot_draw( GtkDrawingArea* plot,
 
 		double* yrange = context->data->y[i].axis == LEFT ?
 			ylrange : yrrange;
+		bool log = ( context->data->y[i].axis == LEFT &&
+				context->ylog ) ||
+			( context->data->y[i].axis == RIGHT &&
+				context->yrlog );
+
+		double x;
+		double xnext;
+		double y;
+		double ynext;
 
 		if ( context->data->y[i].present ) {
 
-			cairo_move_to( cr,
-				m[1] + ( width - m[1] - m[3] ) *
+			if ( context->xlog ) {
+				x = m[1] + ( width - m[1] - m[3] ) *
+					log10( context->data->x / xrange[0] ) /
+					log10( xrange[1] / xrange[0] );
+			} else {
+				x = m[1] + ( width - m[1] - m[3] ) *
 					( context->data->x - xrange[0] ) /
-					( xrange[1] - xrange[0] ),
-				height - m[0] - ( height - m[0] - m[2] ) *
+					( xrange[1] - xrange[0] );
+			}
+
+			if ( log ) {
+				y = height - m[0] - ( height - m[0] - m[2]  ) *
+					log10( context->data->y[i].v / yrange[0] ) /
+					log10( yrange[1] / yrange[0] );
+			} else {
+				y = height - m[0] - ( height - m[0] - m[2] ) *
 					( context->data->y[i].v - yrange[0] ) /
-					( yrange[1] - yrange[0] ) );
+					( yrange[1] - yrange[0] );
+			}
+
+			cairo_move_to( cr, x, y );
 
 		}
 
@@ -1849,28 +1933,70 @@ static void plot_draw( GtkDrawingArea* plot,
 				NULL != data;
 				data = data->next ) {
 
+			if ( context->xlog ) {
+				x = m[1] + ( width - m[1] - m[3] ) *
+					log10( data->x / xrange[0] ) /
+					log10( xrange[1] / xrange[0] );
+			} else {
+				x = m[1] + ( width - m[1] - m[3] ) *
+					( data->x - xrange[0] ) /
+					( xrange[1] - xrange[0] );
+			}
+
 			if ( data->y[i].present ) {
 
-				if ( NULL != data->next && data->next->y[i].present ) {
+				if ( log ) {
+					y = height - m[0] - ( height - m[0] - m[2] ) *
+						log10( data->y[i].v / yrange[0] ) /
+						log10( yrange[1] / yrange[0] );
+				} else {
+					y = height - m[0] - ( height - m[0] - m[2] ) *
+						( data->y[i].v - yrange[0] ) /
+						( yrange[1] - yrange[0] );
+				}
+
+			}
+
+			if ( NULL != data->next ) {
+
+				if ( context->xlog ) {
+					xnext = m[1] + ( width - m[1] - m[3] ) *
+						log10( data->next->x / xrange[0] ) /
+						log10( xrange[1] / xrange[0] );
+				} else {
+					xnext = m[1] + ( width - m[1] - m[3] ) *
+						( data->next->x - xrange[0] ) /
+						( xrange[1] - xrange[0] );
+				}
 
-					cairo_line_to( cr,
-						m[1] + ( width - m[1] - m[3] ) *
-							( data->next->x - xrange[0] ) /
-							( xrange[1] - xrange[0] ),
-						height - m[0] - ( height - m[0] - m[2] ) *
+				if ( data->next->y[i].present ) {
+
+					if ( log ) {
+						ynext = height - m[0] -
+							( height - m[0] - m[2] ) *
+							log10( data->next->y[i].v / yrange[0] ) /
+							log10( yrange[1] / yrange[0] );
+					} else {
+						ynext = height - m[0] -
+							( height - m[0] - m[2] ) *
 							( data->next->y[i].v - yrange[0] ) /
-							( yrange[1] - yrange[0] ) );
+							( yrange[1] - yrange[0] );
+					}
+
+				}
+
+			}
+
+			if ( data->y[i].present ) {
+
+				if ( NULL != data->next && data->next->y[i].present ) {
+
+					cairo_line_to( cr, xnext, ynext );
 
 				} else if ( NULL == data->prev ||
 						!data->prev->y[i].present ) {
 
-					cairo_rectangle( cr,
-						m[1] + ( width - m[1] - m[3] ) *
-							( data->x - xrange[0] ) /
-							( xrange[1] - xrange[0] ),
-						height - m[0] - ( height - m[0] - m[2] ) *
-							( data->y[i].v - yrange[0] ) /
-							( yrange[1] - yrange[0] ) - 1, 2, 2 );
+					cairo_rectangle( cr, x - 1, y - 1, 2, 2 );
 					cairo_fill( cr );
 
 				} else {
@@ -1883,13 +2009,7 @@ static void plot_draw( GtkDrawingArea* plot,
 
 				if ( NULL != data->next && data->next->y[i].present ) {
 
-					cairo_move_to( cr,
-						m[1] + ( width - m[1] - m[3] ) *
-							( data->next->x - xrange[0] ) /
-							( xrange[1] - xrange[0] ),
-						height - m[0] - ( height - m[0] - m[2] ) *
-							( data->next->y[i].v - yrange[0] ) /
-							( yrange[1] - yrange[0] ) );
+					cairo_move_to( cr, xnext, ynext );
 
 				}
 
@@ -2134,10 +2254,10 @@ static void plot_draw( GtkDrawingArea* plot,
 
 }
 
-static int pretty( double* lo, double* up, int ndiv ) {
+static int pretty( double* lo, double* up, int ndiv, bool log ) {
 
 	/* Shamelessly filched from the R language's source code.
-	 * (src/appl/pretty.c) */
+	 * (src/appl/pretty.c), adapted for log scales */
 
 #define rounding_eps 1E-10
 #define h 1.5
@@ -2148,6 +2268,10 @@ static int pretty( double* lo, double* up, int ndiv ) {
 
 	double lo_ = *lo;
 	double up_ = *up;
+	if ( log ) {
+		lo_ = log10( lo_ );
+		up_ = log10( up_ );
+	}
 
 	double dx = up_ - lo_;
 	double cell = fabs( lo_ ) > fabs( up_ ) ? fabs( lo_ ) : fabs( up_ );
@@ -2197,11 +2321,11 @@ static int pretty( double* lo, double* up, int ndiv ) {
 	double ns = floor( lo_ / unit + rounding_eps );
 	double nu = ceil( up_ / unit - rounding_eps );
 
-	while ( ns * unit > *lo + rounding_eps * unit ) {
+	while ( ns * unit > lo_ + rounding_eps * unit ) {
 		ns--;
 	}
 
-	while ( nu * unit < *up - rounding_eps * unit ) {
+	while ( nu * unit < up_ - rounding_eps * unit ) {
 		nu++;
 	}
 
@@ -2225,11 +2349,13 @@ static int pretty( double* lo, double* up, int ndiv ) {
 		ndiv = k;
 	}
 
-	if ( ns * unit < *lo ) {
-		*lo = ns * unit;
+	*lo = ns * unit;
+	if ( log ) {
+		*lo = pow( 10.0, *lo );
 	}
-	if ( nu * unit > *up ) {
-		*up = nu * unit;
+	*up = nu * unit;
+	if ( log ) {
+		*up = pow( 10.0, *up );
 	}
 
 	return ndiv + 1;
-- 
GitLab