1#include <stdio.h>
2#include <math.h>
3#include <malloc.h>
4#include <string.h>
5
6/*
7 * adapted from Paul Heckbert's algorithm on p 657-659 of
8 * Andrew S. Glassner's book, "Graphics Gems"
9 * ISBN 0-12-286166-3
10 *
11 */
12
13#include "tickmarks.h"
14
15#define MAX(a, b) (((a) < (b)) ? (b) : (a))
16
17static double nicenum(double x, int round)
18{
19	int exp;	/* exponent of x */
20	double f;	/* fractional part of x */
21
22	exp = floor(log10(x));
23	f = x / pow(10.0, exp);
24	if (round) {
25		if (f < 1.5)
26			return 1.0 * pow(10.0, exp);
27		if (f < 3.0)
28			return 2.0 * pow(10.0, exp);
29		if (f < 7.0)
30			return 5.0 * pow(10.0, exp);
31		return 10.0 * pow(10.0, exp);
32	}
33	if (f <= 1.0)
34		return 1.0 * pow(10.0, exp);
35	if (f <= 2.0)
36		return 2.0 * pow(10.0, exp);
37	if (f <= 5.0)
38		return 5.0 * pow(10.0, exp);
39	return 10.0 * pow(10.0, exp);
40}
41
42static void shorten(struct tickmark *tm, int nticks, int *power_of_ten,
43			int use_KMG_symbols, int base_offset)
44{
45	const char shorten_chr[] = { 0, 'K', 'M', 'G', 'P', 'E', 0 };
46	int i, l, minshorten, shorten_idx = 0;
47	char *str;
48
49	minshorten = 100;
50	for (i = 0; i < nticks; i++) {
51		str = tm[i].string;
52		l = strlen(str);
53
54		if (strcmp(str, "0") == 0)
55			continue;
56		if (l > 9 && strcmp(&str[l - 9], "000000000") == 0) {
57			*power_of_ten = 9;
58			shorten_idx = 3;
59		} else if (6 < minshorten && l > 6 &&
60				strcmp(&str[l - 6], "000000") == 0) {
61			*power_of_ten = 6;
62			shorten_idx = 2;
63		} else if (l > 3 && strcmp(&str[l - 3], "000") == 0) {
64			*power_of_ten = 3;
65			shorten_idx = 1;
66		} else {
67			*power_of_ten = 0;
68		}
69
70		if (*power_of_ten < minshorten)
71			minshorten = *power_of_ten;
72	}
73
74	if (minshorten == 0)
75		return;
76	if (!use_KMG_symbols)
77		shorten_idx = 0;
78	else if (base_offset)
79		shorten_idx += base_offset;
80
81	for (i = 0; i < nticks; i++) {
82		str = tm[i].string;
83		l = strlen(str);
84		str[l - minshorten] = shorten_chr[shorten_idx];
85		if (shorten_idx)
86			str[l - minshorten + 1] = '\0';
87	}
88}
89
90int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm,
91		int *power_of_ten, int use_KMG_symbols, int base_offset)
92{
93	char str[100];
94	int nfrac;
95	double d;	/* tick mark spacing */
96	double graphmin, graphmax;	/* graph range min and max */
97	double range, x;
98	int count, i;
99
100	/* we expect min != max */
101	range = nicenum(max - min, 0);
102	d = nicenum(range / (nticks - 1), 1);
103	graphmin = floor(min / d) * d;
104	graphmax = ceil(max / d) * d;
105	nfrac = MAX(-floor(log10(d)), 0);
106	snprintf(str, sizeof(str)-1, "%%.%df", nfrac);
107
108	count = ((graphmax + 0.5 * d) - graphmin) / d + 1;
109	*tm = malloc(sizeof(**tm) * count);
110
111	i = 0;
112	for (x = graphmin; x < graphmax + 0.5 * d; x += d) {
113		(*tm)[i].value = x;
114		sprintf((*tm)[i].string, str, x);
115		i++;
116	}
117	shorten(*tm, i, power_of_ten, use_KMG_symbols, base_offset);
118	return i;
119}
120
121#if 0
122
123static void test_range(double x, double y)
124{
125	int nticks, i;
126
127	struct tickmark *tm = NULL;
128	printf("Testing range %g - %g\n", x, y);
129	nticks = calc_tickmarks(x, y, 10, &tm);
130
131	for (i = 0; i < nticks; i++)
132		printf("   (%s) %g\n", tm[i].string, tm[i].value);
133
134	printf("\n\n");
135	free(tm);
136}
137
138int main(int argc, char *argv[])
139{
140	test_range(0.0005, 0.008);
141	test_range(0.5, 0.8);
142	test_range(5.5, 8.8);
143	test_range(50.5, 80.8);
144	test_range(-20, 20.8);
145	test_range(-30, 700.8);
146}
147#endif
148