|
@@ -175,16 +175,14 @@ struct BaseGraphWindow : Window {
|
|
|
protected:
|
|
|
enum {
|
|
|
GRAPH_MAX_DATASETS = 32,
|
|
|
GRAPH_AXIS_LINE_COLOUR = 215,
|
|
|
GRAPH_NUM_MONTHS = 24, ///< Number of months displayed in the graph.
|
|
|
|
|
|
GRAPH_NUM_LINES_Y = 9, ///< How many horizontal lines to draw.
|
|
|
/* 9 is convenient as that means the distance between them is the gd_height of the graph / 8,
|
|
|
* which is the same
|
|
|
* as height >> 3. */
|
|
|
MIN_GRAPH_NUM_LINES_Y = 9, ///< Minimal number of horizontal lines to draw.
|
|
|
MIN_GRID_PIXEL_SIZE = 20, ///< Minimum distance between graph lines.
|
|
|
};
|
|
|
|
|
|
uint excluded_data; ///< bitmask of the datasets that shouldn't be displayed.
|
|
|
byte num_dataset;
|
|
|
byte num_on_x_axis;
|
|
|
byte num_vert_lines;
|
|
@@ -205,15 +203,16 @@ protected:
|
|
|
byte colours[GRAPH_MAX_DATASETS];
|
|
|
OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][GRAPH_NUM_MONTHS]; ///< Stored costs for the last #GRAPH_NUM_MONTHS months
|
|
|
|
|
|
/**
|
|
|
* Get the interval that contains the graph's data. Excluded data is ignored to show smaller values in
|
|
|
* better detail when disabling higher ones.
|
|
|
* @param num_hori_lines Number of horizontal lines to be drawn.
|
|
|
* @return Highest and lowest values of the graph (ignoring disabled data).
|
|
|
*/
|
|
|
ValuesInterval GetValuesInterval() const
|
|
|
ValuesInterval GetValuesInterval(int num_hori_lines) const
|
|
|
{
|
|
|
ValuesInterval current_interval;
|
|
|
current_interval.highest = INT64_MIN;
|
|
|
current_interval.lowest = INT64_MAX;
|
|
|
|
|
|
for (int i = 0; i < this->num_dataset; i++) {
|
|
@@ -235,64 +234,66 @@ protected:
|
|
|
/* Always include zero in the shown range. */
|
|
|
OverflowSafeInt64 abs_lower = (current_interval.lowest > 0) ? (OverflowSafeInt64)0 : abs(current_interval.lowest);
|
|
|
OverflowSafeInt64 abs_higher = (current_interval.highest < 0) ? (OverflowSafeInt64)0 : current_interval.highest;
|
|
|
|
|
|
int num_pos_grids;
|
|
|
int grid_size;
|
|
|
const int num_grids = GRAPH_NUM_LINES_Y - 1;
|
|
|
|
|
|
if (abs_lower == 0) {
|
|
|
if (abs_higher == 0) {
|
|
|
/* If both values are zero, show an empty graph. */
|
|
|
num_pos_grids = num_grids / 2;
|
|
|
num_pos_grids = num_hori_lines / 2;
|
|
|
grid_size = 1;
|
|
|
} else {
|
|
|
/* The positive part of the graph can use all grids. */
|
|
|
num_pos_grids = num_grids;
|
|
|
grid_size = ceil((float)abs_higher / num_grids);
|
|
|
num_pos_grids = num_hori_lines;
|
|
|
grid_size = ceil((float)abs_higher / num_hori_lines);
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
if (abs_higher == 0) {
|
|
|
/* The negative part of the graph can use all grids. */
|
|
|
num_pos_grids = 0;
|
|
|
grid_size = ceil((float)abs_lower / num_grids);
|
|
|
grid_size = ceil((float)abs_lower / num_hori_lines);
|
|
|
} else {
|
|
|
/* Get the smallest grid size required and the number of grids for each part of the graph. */
|
|
|
int min_pos_grids = 1;
|
|
|
int min_grid_size = INT_MAX;
|
|
|
for (num_pos_grids = 1; num_pos_grids <= num_grids - 1; num_pos_grids++) {
|
|
|
for (num_pos_grids = 1; num_pos_grids <= num_hori_lines - 1; num_pos_grids++) {
|
|
|
/* Size required for each part of the graph given this number of grids. */
|
|
|
int pos_grid_size = ceil((float)abs_higher / num_pos_grids);
|
|
|
int neg_grid_size = ceil((float)abs_lower / (num_grids - num_pos_grids));
|
|
|
int neg_grid_size = ceil((float)abs_lower / (num_hori_lines - num_pos_grids));
|
|
|
grid_size = max(pos_grid_size, neg_grid_size);
|
|
|
if (grid_size < min_grid_size) {
|
|
|
min_pos_grids = num_pos_grids;
|
|
|
min_grid_size = grid_size;
|
|
|
}
|
|
|
}
|
|
|
grid_size = min_grid_size;
|
|
|
num_pos_grids = min_pos_grids;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
current_interval.highest = num_pos_grids * grid_size;
|
|
|
current_interval.lowest = -(num_grids - num_pos_grids) * grid_size;
|
|
|
current_interval.lowest = -(num_hori_lines - num_pos_grids) * grid_size;
|
|
|
return current_interval;
|
|
|
}
|
|
|
|
|
|
/** Get width for Y labels */
|
|
|
uint GetYLabelWidth(ValuesInterval current_interval) const
|
|
|
/** Get width for Y labels.
|
|
|
* @param current_interval Interval that contains all of the graph data.
|
|
|
* @param num_hori_lines Number of horizontal lines to be drawn.
|
|
|
*/
|
|
|
uint GetYLabelWidth(ValuesInterval current_interval, int num_hori_lines) const
|
|
|
{
|
|
|
/* draw text strings on the y axis */
|
|
|
int64 y_label = current_interval.highest;
|
|
|
int64 y_label_separation = (current_interval.highest - current_interval.lowest) / (GRAPH_NUM_LINES_Y - 1);
|
|
|
int64 y_label_separation = (current_interval.highest - current_interval.lowest) / num_hori_lines;
|
|
|
|
|
|
uint max_width = 0;
|
|
|
|
|
|
for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
|
|
|
for (int i = 0; i < (num_hori_lines + 1); i++) {
|
|
|
SetDParam(0, this->format_str_y_axis);
|
|
|
SetDParam(1, y_label);
|
|
|
Dimension d = GetStringBoundingBox(STR_GRAPH_Y_LABEL);
|
|
|
if (d.width > max_width) max_width = d.width;
|
|
|
|
|
|
y_label -= y_label_separation;
|
|
@@ -322,25 +323,31 @@ protected:
|
|
|
* placed outside the area. */
|
|
|
r.top += 5 + GetCharacterHeight(FS_SMALL) / 2;
|
|
|
r.bottom -= (this->month == 0xFF ? 1 : 3) * GetCharacterHeight(FS_SMALL) + 4;
|
|
|
r.left += 9;
|
|
|
r.right -= 5;
|
|
|
|
|
|
interval = GetValuesInterval();
|
|
|
/* Initial number of horizontal lines. */
|
|
|
int num_hori_lines = 160 / MIN_GRID_PIXEL_SIZE;
|
|
|
/* For the rest of the height, the number of horizontal lines will increase more slowly. */
|
|
|
int resize = (r.bottom - r.top - 160) / (2 * MIN_GRID_PIXEL_SIZE);
|
|
|
if (resize > 0) num_hori_lines += resize;
|
|
|
|
|
|
int label_width = GetYLabelWidth(interval);
|
|
|
interval = GetValuesInterval(num_hori_lines);
|
|
|
|
|
|
int label_width = GetYLabelWidth(interval, num_hori_lines);
|
|
|
|
|
|
r.left += label_width;
|
|
|
|
|
|
int x_sep = (r.right - r.left) / this->num_vert_lines;
|
|
|
int y_sep = (r.bottom - r.top) / (GRAPH_NUM_LINES_Y - 1);
|
|
|
int y_sep = (r.bottom - r.top) / num_hori_lines;
|
|
|
|
|
|
/* Redetermine right and bottom edge of graph to fit with the integer
|
|
|
* separation values. */
|
|
|
r.right = r.left + x_sep * this->num_vert_lines;
|
|
|
r.bottom = r.top + y_sep * (GRAPH_NUM_LINES_Y - 1);
|
|
|
r.bottom = r.top + y_sep * num_hori_lines;
|
|
|
|
|
|
OverflowSafeInt64 interval_size = interval.highest + abs(interval.lowest);
|
|
|
/* Where to draw the X axis */
|
|
|
x_axis_offset = (r.bottom - r.top) * interval.highest / interval_size;
|
|
|
|
|
|
/* Draw the vertical grid lines. */
|
|
@@ -353,13 +360,13 @@ protected:
|
|
|
x += x_sep;
|
|
|
}
|
|
|
|
|
|
/* Draw the horizontal grid lines. */
|
|
|
y = r.bottom;
|
|
|
|
|
|
for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
|
|
|
for (int i = 0; i < (num_hori_lines + 1); i++) {
|
|
|
GfxFillRect(r.left - 3, y, r.left - 1, y, GRAPH_AXIS_LINE_COLOUR);
|
|
|
GfxFillRect(r.left, y, r.right, y, grid_colour);
|
|
|
y -= y_sep;
|
|
|
}
|
|
|
|
|
|
/* Draw the y axis. */
|
|
@@ -375,17 +382,17 @@ protected:
|
|
|
|
|
|
assert(this->num_on_x_axis > 0);
|
|
|
assert(this->num_dataset > 0);
|
|
|
|
|
|
/* draw text strings on the y axis */
|
|
|
int64 y_label = interval.highest;
|
|
|
int64 y_label_separation = abs(interval.highest - interval.lowest) / (GRAPH_NUM_LINES_Y - 1);
|
|
|
int64 y_label_separation = abs(interval.highest - interval.lowest) / num_hori_lines;
|
|
|
|
|
|
y = r.top - GetCharacterHeight(FS_SMALL) / 2;
|
|
|
|
|
|
for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
|
|
|
for (int i = 0; i < (num_hori_lines + 1); i++) {
|
|
|
SetDParam(0, this->format_str_y_axis);
|
|
|
SetDParam(1, y_label);
|
|
|
DrawString(r.left - label_width - 4, r.left - 4, y, STR_GRAPH_Y_LABEL, graph_axis_label_colour, SA_RIGHT);
|
|
|
|
|
|
y_label -= y_label_separation;
|
|
|
y += y_sep;
|
|
@@ -533,13 +540,13 @@ public:
|
|
|
|
|
|
SetDParam(0, this->format_str_y_axis);
|
|
|
SetDParam(1, INT64_MAX);
|
|
|
uint y_label_width = GetStringBoundingBox(STR_GRAPH_Y_LABEL).width;
|
|
|
|
|
|
size->width = max<uint>(size->width, 5 + y_label_width + this->num_on_x_axis * (x_label_width + 5) + 9);
|
|
|
size->height = max<uint>(size->height, 5 + (1 + GRAPH_NUM_LINES_Y * 2 + (this->month != 0xFF ? 3 : 1)) * FONT_HEIGHT_SMALL + 4);
|
|
|
size->height = max<uint>(size->height, 5 + (1 + MIN_GRAPH_NUM_LINES_Y * 2 + (this->month != 0xFF ? 3 : 1)) * FONT_HEIGHT_SMALL + 4);
|
|
|
size->height = max<uint>(size->height, size->width / 3);
|
|
|
}
|
|
|
|
|
|
virtual void DrawWidget(const Rect &r, int widget) const
|
|
|
{
|
|
|
if (widget != this->graph_widget) return;
|