src/graph_gui.cpp
changeset 10612 bc9b5cae8f95
parent 10611 19de4d762169
child 10641 13148f508e6d
equal deleted inserted replaced
10611:19de4d762169 10612:bc9b5cae8f95
    22 
    22 
    23 /* Bitmasks of player and cargo indices that shouldn't be drawn. */
    23 /* Bitmasks of player and cargo indices that shouldn't be drawn. */
    24 static uint _legend_excluded_players;
    24 static uint _legend_excluded_players;
    25 static uint _legend_excluded_cargo;
    25 static uint _legend_excluded_cargo;
    26 
    26 
    27 /************************/
       
    28 /* GENERIC GRAPH DRAWER */
       
    29 /************************/
       
    30 
       
    31 enum {
       
    32 	GRAPH_MAX_DATASETS = 32,
       
    33 	GRAPH_AXIS_LABEL_COLOUR = TC_BLACK,
       
    34 	GRAPH_AXIS_LINE_COLOUR  = 215,
       
    35 
       
    36 	GRAPH_X_POSITION_BEGINNING  = 44,  ///< Start the graph 44 pixels from gw->left
       
    37 	GRAPH_X_POSITION_SEPARATION = 22,  ///< There are 22 pixels between each X value
       
    38 
       
    39 	GRAPH_NUM_LINES_Y = 9, ///< How many horizontal lines to draw.
       
    40 	/* 9 is convenient as that means the distance between them is the height of the graph / 8,
       
    41 	 * which is the same
       
    42 	 * as height >> 3. */
       
    43 };
       
    44 
       
    45 /* Apparently these don't play well with enums. */
    27 /* Apparently these don't play well with enums. */
    46 static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
    28 static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
    47 static const uint INVALID_DATAPOINT_POS = UINT_MAX;  // Used to determine if the previous point was drawn.
    29 static const uint INVALID_DATAPOINT_POS = UINT_MAX;  // Used to determine if the previous point was drawn.
    48 
    30 
    49 struct GraphDrawer {
       
    50 	uint excluded_data; ///< bitmask of the datasets that shouldn't be displayed.
       
    51 	byte num_dataset;
       
    52 	byte num_on_x_axis;
       
    53 	bool has_negative_values;
       
    54 	byte num_vert_lines;
       
    55 
       
    56 	/* The starting month and year that values are plotted against. If month is
       
    57 	 * 0xFF, use x_values_start and x_values_increment below instead. */
       
    58 	byte month;
       
    59 	Year year;
       
    60 
       
    61 	/* These values are used if the graph is being plotted against values
       
    62 	 * rather than the dates specified by month and year. */
       
    63 	uint16 x_values_start;
       
    64 	uint16 x_values_increment;
       
    65 
       
    66 	int left, top;  ///< Where to start drawing the graph, in pixels.
       
    67 	uint height;    ///< The height of the graph in pixels.
       
    68 	StringID format_str_y_axis;
       
    69 	byte colors[GRAPH_MAX_DATASETS];
       
    70 	OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][24]; ///< last 2 years
       
    71 };
       
    72 
       
    73 static void DrawGraph(const GraphDrawer *gw)
       
    74 {
       
    75 	uint x, y;                       ///< Reused whenever x and y coordinates are needed.
       
    76 	OverflowSafeInt64 highest_value; ///< Highest value to be drawn.
       
    77 	int x_axis_offset;               ///< Distance from the top of the graph to the x axis.
       
    78 
       
    79 	/* the colors and cost array of GraphDrawer must accomodate
       
    80 	 * both values for cargo and players. So if any are higher, quit */
       
    81 	assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_PLAYERS);
       
    82 	assert(gw->num_vert_lines > 0);
       
    83 
       
    84 	byte grid_colour = _colour_gradient[14][4];
       
    85 
       
    86 	/* The coordinates of the opposite edges of the graph. */
       
    87 	int bottom = gw->top + gw->height - 1;
       
    88 	int right  = gw->left + GRAPH_X_POSITION_BEGINNING + gw->num_vert_lines * GRAPH_X_POSITION_SEPARATION - 1;
       
    89 
       
    90 	/* Draw the vertical grid lines. */
       
    91 
       
    92 	/* Don't draw the first line, as that's where the axis will be. */
       
    93 	x = gw->left + GRAPH_X_POSITION_BEGINNING + GRAPH_X_POSITION_SEPARATION;
       
    94 
       
    95 	for (int i = 0; i < gw->num_vert_lines; i++) {
       
    96 		GfxFillRect(x, gw->top, x, bottom, grid_colour);
       
    97 		x += GRAPH_X_POSITION_SEPARATION;
       
    98 	}
       
    99 
       
   100 	/* Draw the horizontal grid lines. */
       
   101 	x = gw->left + GRAPH_X_POSITION_BEGINNING;
       
   102 	y = gw->height + gw->top;
       
   103 
       
   104 	for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   105 		GfxFillRect(x, y, right, y, grid_colour);
       
   106 		y -= (gw->height / (GRAPH_NUM_LINES_Y - 1));
       
   107 	}
       
   108 
       
   109 	/* Draw the y axis. */
       
   110 	GfxFillRect(x, gw->top, x, bottom, GRAPH_AXIS_LINE_COLOUR);
       
   111 
       
   112 	/* Find the distance from the top of the graph to the x axis. */
       
   113 	x_axis_offset = gw->height;
       
   114 
       
   115 	/* The graph is currently symmetrical about the x axis. */
       
   116 	if (gw->has_negative_values) x_axis_offset /= 2;
       
   117 
       
   118 	/* Draw the x axis. */
       
   119 	y = x_axis_offset + gw->top;
       
   120 	GfxFillRect(x, y, right, y, GRAPH_AXIS_LINE_COLOUR);
       
   121 
       
   122 	/* Find the largest value that will be drawn. */
       
   123 	if (gw->num_on_x_axis == 0)
       
   124 		return;
       
   125 
       
   126 	assert(gw->num_on_x_axis > 0);
       
   127 	assert(gw->num_dataset > 0);
       
   128 
       
   129 	/* Start of with a value of twice the height of the graph in pixels. It's a
       
   130 	 * bit arbitrary, but it makes the cargo payment graph look a little nicer,
       
   131 	 * and prevents division by zero when calculating where the datapoint
       
   132 	 * should be drawn. */
       
   133 	highest_value = x_axis_offset * 2;
       
   134 
       
   135 	for (int i = 0; i < gw->num_dataset; i++) {
       
   136 		if (!HasBit(gw->excluded_data, i)) {
       
   137 			for (int j = 0; j < gw->num_on_x_axis; j++) {
       
   138 				OverflowSafeInt64 datapoint = gw->cost[i][j];
       
   139 
       
   140 				if (datapoint != INVALID_DATAPOINT) {
       
   141 					/* For now, if the graph has negative values the scaling is
       
   142 					 * symmetrical about the x axis, so take the absolute value
       
   143 					 * of each data point. */
       
   144 					highest_value = max(highest_value, abs(datapoint));
       
   145 				}
       
   146 			}
       
   147 		}
       
   148 	}
       
   149 
       
   150 	/* Round up highest_value so that it will divide cleanly into the number of
       
   151 	 * axis labels used. */
       
   152 	int round_val = highest_value % (GRAPH_NUM_LINES_Y - 1);
       
   153 	if (round_val != 0) highest_value += (GRAPH_NUM_LINES_Y - 1 - round_val);
       
   154 
       
   155 	/* draw text strings on the y axis */
       
   156 	int64 y_label = highest_value;
       
   157 	int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
       
   158 
       
   159 	/* If there are negative values, the graph goes from highest_value to
       
   160 	 * -highest_value, not highest_value to 0. */
       
   161 	if (gw->has_negative_values) y_label_separation *= 2;
       
   162 
       
   163 	x = gw->left + GRAPH_X_POSITION_BEGINNING + 1;
       
   164 	y = gw->top - 3;
       
   165 
       
   166 	for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   167 		SetDParam(0, gw->format_str_y_axis);
       
   168 		SetDParam(1, y_label);
       
   169 		DrawStringRightAligned(x, y, STR_0170, GRAPH_AXIS_LABEL_COLOUR);
       
   170 
       
   171 		y_label -= y_label_separation;
       
   172 		y += (gw->height / (GRAPH_NUM_LINES_Y - 1));
       
   173 	}
       
   174 
       
   175 	/* draw strings on the x axis */
       
   176 	if (gw->month != 0xFF) {
       
   177 		x = gw->left + GRAPH_X_POSITION_BEGINNING;
       
   178 		y = gw->top + gw->height + 1;
       
   179 		byte month = gw->month;
       
   180 		Year year  = gw->year;
       
   181 		for (int i = 0; i < gw->num_on_x_axis; i++) {
       
   182 			SetDParam(0, month + STR_0162_JAN);
       
   183 			SetDParam(1, month + STR_0162_JAN + 2);
       
   184 			SetDParam(2, year);
       
   185 			DrawString(x, y, month == 0 ? STR_016F : STR_016E, GRAPH_AXIS_LABEL_COLOUR);
       
   186 
       
   187 			month += 3;
       
   188 			if (month >= 12) {
       
   189 				month = 0;
       
   190 				year++;
       
   191 			}
       
   192 			x += GRAPH_X_POSITION_SEPARATION;
       
   193 		}
       
   194 	} else {
       
   195 		/* Draw the label under the data point rather than on the grid line. */
       
   196 		x = gw->left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2) + 1;
       
   197 		y = gw->top + gw->height + 1;
       
   198 		uint16 label = gw->x_values_start;
       
   199 
       
   200 		for (int i = 0; i < gw->num_on_x_axis; i++) {
       
   201 			SetDParam(0, label);
       
   202 			DrawStringCentered(x, y, STR_01CB, GRAPH_AXIS_LABEL_COLOUR);
       
   203 
       
   204 			label += gw->x_values_increment;
       
   205 			x += GRAPH_X_POSITION_SEPARATION;
       
   206 		}
       
   207 	}
       
   208 
       
   209 	/* draw lines and dots */
       
   210 	for (int i = 0; i < gw->num_dataset; i++) {
       
   211 		if (!HasBit(gw->excluded_data, i)) {
       
   212 			/* Centre the dot between the grid lines. */
       
   213 			x = gw->left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2);
       
   214 
       
   215 			byte color  = gw->colors[i];
       
   216 			uint prev_x = INVALID_DATAPOINT_POS;
       
   217 			uint prev_y = INVALID_DATAPOINT_POS;
       
   218 
       
   219 			for (int j = 0; j < gw->num_on_x_axis; j++) {
       
   220 				OverflowSafeInt64 datapoint = gw->cost[i][j];
       
   221 
       
   222 				if (datapoint != INVALID_DATAPOINT) {
       
   223 					/*
       
   224 					 * Check whether we need to reduce the 'accuracy' of the
       
   225 					 * datapoint value and the highest value to splut overflows.
       
   226 					 * And when 'drawing' 'one million' or 'one million and one'
       
   227 					 * there is no significant difference, so the least
       
   228 					 * significant bits can just be removed.
       
   229 					 *
       
   230 					 * If there are more bits needed than would fit in a 32 bits
       
   231 					 * integer, so at about 31 bits because of the sign bit, the
       
   232 					 * least significant bits are removed.
       
   233 					 */
       
   234 					int mult_range = FindLastBit(x_axis_offset) + FindLastBit(abs(datapoint));
       
   235 					int reduce_range = max(mult_range - 31, 0);
       
   236 
       
   237 					/* Handle negative values differently (don't shift sign) */
       
   238 					if (datapoint < 0) {
       
   239 						datapoint = -(abs(datapoint) >> reduce_range);
       
   240 					} else {
       
   241 						datapoint >>= reduce_range;
       
   242 					}
       
   243 
       
   244 					y = gw->top + x_axis_offset - (x_axis_offset * datapoint) / (highest_value >> reduce_range);
       
   245 
       
   246 					/* Draw the point. */
       
   247 					GfxFillRect(x - 1, y - 1, x + 1, y + 1, color);
       
   248 
       
   249 					/* Draw the line connected to the previous point. */
       
   250 					if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, color);
       
   251 
       
   252 					prev_x = x;
       
   253 					prev_y = y;
       
   254 				} else {
       
   255 					prev_x = INVALID_DATAPOINT_POS;
       
   256 					prev_y = INVALID_DATAPOINT_POS;
       
   257 				}
       
   258 
       
   259 				x += GRAPH_X_POSITION_SEPARATION;
       
   260 			}
       
   261 		}
       
   262 	}
       
   263 }
       
   264 
       
   265 /****************/
    31 /****************/
   266 /* GRAPH LEGEND */
    32 /* GRAPH LEGEND */
   267 /****************/
    33 /****************/
   268 
    34 
   269 struct GraphLegendWindow : Window {
    35 struct GraphLegendWindow : Window {
   270 	GraphLegendWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
    36 	GraphLegendWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
   271 	{
    37 	{
   272 		for (uint i = 3; i < this->widget_count; i++) {
    38 		for (uint i = 3; i < this->widget_count; i++) {
   273 			if (!HasBit(_legend_excluded_players, i - 3)) this->LowerWidget(i);
    39 			if (!HasBit(_legend_excluded_players, i - 3)) this->LowerWidget(i);
   274 		}
    40 		}
       
    41 
       
    42 		this->FindWindowPlacementAndResize(desc);
   275 	}
    43 	}
   276 
    44 
   277 	virtual void OnPaint()
    45 	virtual void OnPaint()
   278 	{
    46 	{
   279 		const Player *p;
    47 		const Player *p;
   339 static void ShowGraphLegend()
   107 static void ShowGraphLegend()
   340 {
   108 {
   341 	AllocateWindowDescFront<GraphLegendWindow>(&_graph_legend_desc, 0);
   109 	AllocateWindowDescFront<GraphLegendWindow>(&_graph_legend_desc, 0);
   342 }
   110 }
   343 
   111 
       
   112 /******************/
       
   113 /* BASE OF GRAPHS */
       
   114 /*****************/
       
   115 
       
   116 struct BaseGraphWindow : Window {
       
   117 protected:
       
   118 	enum {
       
   119 		GRAPH_MAX_DATASETS = 32,
       
   120 		GRAPH_AXIS_LABEL_COLOUR = TC_BLACK,
       
   121 		GRAPH_AXIS_LINE_COLOUR  = 215,
       
   122 
       
   123 		GRAPH_X_POSITION_BEGINNING  = 44,  ///< Start the graph 44 pixels from gd_left
       
   124 		GRAPH_X_POSITION_SEPARATION = 22,  ///< There are 22 pixels between each X value
       
   125 
       
   126 		GRAPH_NUM_LINES_Y = 9, ///< How many horizontal lines to draw.
       
   127 		/* 9 is convenient as that means the distance between them is the gd_height of the graph / 8,
       
   128 		* which is the same
       
   129 		* as height >> 3. */
       
   130 	};
       
   131 
       
   132 	uint excluded_data; ///< bitmask of the datasets that shouldn't be displayed.
       
   133 	byte num_dataset;
       
   134 	byte num_on_x_axis;
       
   135 	bool has_negative_values;
       
   136 	byte num_vert_lines;
       
   137 
       
   138 	/* The starting month and year that values are plotted against. If month is
       
   139 	 * 0xFF, use x_values_start and x_values_increment below instead. */
       
   140 	byte month;
       
   141 	Year year;
       
   142 
       
   143 	/* These values are used if the graph is being plotted against values
       
   144 	 * rather than the dates specified by month and year. */
       
   145 	uint16 x_values_start;
       
   146 	uint16 x_values_increment;
       
   147 
       
   148 	int gd_left, gd_top;  ///< Where to start drawing the graph, in pixels.
       
   149 	uint gd_height;    ///< The height of the graph in pixels.
       
   150 	StringID format_str_y_axis;
       
   151 	byte colors[GRAPH_MAX_DATASETS];
       
   152 	OverflowSafeInt64 cost[GRAPH_MAX_DATASETS][24]; ///< last 2 years
       
   153 
       
   154 	void DrawGraph() const
       
   155 	{
       
   156 		uint x, y;                       ///< Reused whenever x and y coordinates are needed.
       
   157 		OverflowSafeInt64 highest_value; ///< Highest value to be drawn.
       
   158 		int x_axis_offset;               ///< Distance from the top of the graph to the x axis.
       
   159 
       
   160 		/* the colors and cost array of GraphDrawer must accomodate
       
   161 		* both values for cargo and players. So if any are higher, quit */
       
   162 		assert(GRAPH_MAX_DATASETS >= (int)NUM_CARGO && GRAPH_MAX_DATASETS >= (int)MAX_PLAYERS);
       
   163 		assert(this->num_vert_lines > 0);
       
   164 
       
   165 		byte grid_colour = _colour_gradient[14][4];
       
   166 
       
   167 		/* The coordinates of the opposite edges of the graph. */
       
   168 		int bottom = this->gd_top + this->gd_height - 1;
       
   169 		int right  = this->gd_left + GRAPH_X_POSITION_BEGINNING + this->num_vert_lines * GRAPH_X_POSITION_SEPARATION - 1;
       
   170 
       
   171 		/* Draw the vertical grid lines. */
       
   172 
       
   173 		/* Don't draw the first line, as that's where the axis will be. */
       
   174 		x = this->gd_left + GRAPH_X_POSITION_BEGINNING + GRAPH_X_POSITION_SEPARATION;
       
   175 
       
   176 		for (int i = 0; i < this->num_vert_lines; i++) {
       
   177 			GfxFillRect(x, this->gd_top, x, bottom, grid_colour);
       
   178 			x += GRAPH_X_POSITION_SEPARATION;
       
   179 		}
       
   180 
       
   181 		/* Draw the horizontal grid lines. */
       
   182 		x = this->gd_left + GRAPH_X_POSITION_BEGINNING;
       
   183 		y = this->gd_height + this->gd_top;
       
   184 
       
   185 		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   186 			GfxFillRect(x, y, right, y, grid_colour);
       
   187 			y -= (this->gd_height / (GRAPH_NUM_LINES_Y - 1));
       
   188 		}
       
   189 
       
   190 		/* Draw the y axis. */
       
   191 		GfxFillRect(x, this->gd_top, x, bottom, GRAPH_AXIS_LINE_COLOUR);
       
   192 
       
   193 		/* Find the distance from the gd_top of the graph to the x axis. */
       
   194 		x_axis_offset = this->gd_height;
       
   195 
       
   196 		/* The graph is currently symmetrical about the x axis. */
       
   197 		if (this->has_negative_values) x_axis_offset /= 2;
       
   198 
       
   199 		/* Draw the x axis. */
       
   200 		y = x_axis_offset + this->gd_top;
       
   201 		GfxFillRect(x, y, right, y, GRAPH_AXIS_LINE_COLOUR);
       
   202 
       
   203 		/* Find the largest value that will be drawn. */
       
   204 		if (this->num_on_x_axis == 0)
       
   205 			return;
       
   206 
       
   207 		assert(this->num_on_x_axis > 0);
       
   208 		assert(this->num_dataset > 0);
       
   209 
       
   210 		/* Start of with a value of twice the gd_height of the graph in pixels. It's a
       
   211 		* bit arbitrary, but it makes the cargo payment graph look a little nicer,
       
   212 		* and prevents division by zero when calculating where the datapoint
       
   213 		* should be drawn. */
       
   214 		highest_value = x_axis_offset * 2;
       
   215 
       
   216 		for (int i = 0; i < this->num_dataset; i++) {
       
   217 			if (!HasBit(this->excluded_data, i)) {
       
   218 				for (int j = 0; j < this->num_on_x_axis; j++) {
       
   219 					OverflowSafeInt64 datapoint = this->cost[i][j];
       
   220 
       
   221 					if (datapoint != INVALID_DATAPOINT) {
       
   222 						/* For now, if the graph has negative values the scaling is
       
   223 						* symmetrical about the x axis, so take the absolute value
       
   224 						* of each data point. */
       
   225 						highest_value = max(highest_value, abs(datapoint));
       
   226 					}
       
   227 				}
       
   228 			}
       
   229 		}
       
   230 
       
   231 		/* Round up highest_value so that it will divide cleanly into the number of
       
   232 		* axis labels used. */
       
   233 		int round_val = highest_value % (GRAPH_NUM_LINES_Y - 1);
       
   234 		if (round_val != 0) highest_value += (GRAPH_NUM_LINES_Y - 1 - round_val);
       
   235 
       
   236 		/* draw text strings on the y axis */
       
   237 		int64 y_label = highest_value;
       
   238 		int64 y_label_separation = highest_value / (GRAPH_NUM_LINES_Y - 1);
       
   239 
       
   240 		/* If there are negative values, the graph goes from highest_value to
       
   241 		* -highest_value, not highest_value to 0. */
       
   242 		if (this->has_negative_values) y_label_separation *= 2;
       
   243 
       
   244 		x = this->gd_left + GRAPH_X_POSITION_BEGINNING + 1;
       
   245 		y = this->gd_top - 3;
       
   246 
       
   247 		for (int i = 0; i < GRAPH_NUM_LINES_Y; i++) {
       
   248 			SetDParam(0, this->format_str_y_axis);
       
   249 			SetDParam(1, y_label);
       
   250 			DrawStringRightAligned(x, y, STR_0170, GRAPH_AXIS_LABEL_COLOUR);
       
   251 
       
   252 			y_label -= y_label_separation;
       
   253 			y += (this->gd_height / (GRAPH_NUM_LINES_Y - 1));
       
   254 		}
       
   255 
       
   256 		/* draw strings on the x axis */
       
   257 		if (this->month != 0xFF) {
       
   258 			x = this->gd_left + GRAPH_X_POSITION_BEGINNING;
       
   259 			y = this->gd_top + this->gd_height + 1;
       
   260 			byte month = this->month;
       
   261 			Year year  = this->year;
       
   262 			for (int i = 0; i < this->num_on_x_axis; i++) {
       
   263 				SetDParam(0, month + STR_0162_JAN);
       
   264 				SetDParam(1, month + STR_0162_JAN + 2);
       
   265 				SetDParam(2, year);
       
   266 				DrawString(x, y, month == 0 ? STR_016F : STR_016E, GRAPH_AXIS_LABEL_COLOUR);
       
   267 
       
   268 				month += 3;
       
   269 				if (month >= 12) {
       
   270 					month = 0;
       
   271 					year++;
       
   272 				}
       
   273 				x += GRAPH_X_POSITION_SEPARATION;
       
   274 			}
       
   275 		} else {
       
   276 			/* Draw the label under the data point rather than on the grid line. */
       
   277 			x = this->gd_left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2) + 1;
       
   278 			y = this->gd_top + this->gd_height + 1;
       
   279 			uint16 label = this->x_values_start;
       
   280 
       
   281 			for (int i = 0; i < this->num_on_x_axis; i++) {
       
   282 				SetDParam(0, label);
       
   283 				DrawStringCentered(x, y, STR_01CB, GRAPH_AXIS_LABEL_COLOUR);
       
   284 
       
   285 				label += this->x_values_increment;
       
   286 				x += GRAPH_X_POSITION_SEPARATION;
       
   287 			}
       
   288 		}
       
   289 
       
   290 		/* draw lines and dots */
       
   291 		for (int i = 0; i < this->num_dataset; i++) {
       
   292 			if (!HasBit(this->excluded_data, i)) {
       
   293 				/* Centre the dot between the grid lines. */
       
   294 				x = this->gd_left + GRAPH_X_POSITION_BEGINNING + (GRAPH_X_POSITION_SEPARATION / 2);
       
   295 
       
   296 				byte color  = this->colors[i];
       
   297 				uint prev_x = INVALID_DATAPOINT_POS;
       
   298 				uint prev_y = INVALID_DATAPOINT_POS;
       
   299 
       
   300 				for (int j = 0; j < this->num_on_x_axis; j++) {
       
   301 					OverflowSafeInt64 datapoint = this->cost[i][j];
       
   302 
       
   303 					if (datapoint != INVALID_DATAPOINT) {
       
   304 						/*
       
   305 						* Check whether we need to reduce the 'accuracy' of the
       
   306 						* datapoint value and the highest value to splut overflows.
       
   307 						* And when 'drawing' 'one million' or 'one million and one'
       
   308 						* there is no significant difference, so the least
       
   309 						* significant bits can just be removed.
       
   310 						*
       
   311 						* If there are more bits needed than would fit in a 32 bits
       
   312 						* integer, so at about 31 bits because of the sign bit, the
       
   313 						* least significant bits are removed.
       
   314 						*/
       
   315 						int mult_range = FindLastBit(x_axis_offset) + FindLastBit(abs(datapoint));
       
   316 						int reduce_range = max(mult_range - 31, 0);
       
   317 
       
   318 						/* Handle negative values differently (don't shift sign) */
       
   319 						if (datapoint < 0) {
       
   320 							datapoint = -(abs(datapoint) >> reduce_range);
       
   321 						} else {
       
   322 							datapoint >>= reduce_range;
       
   323 						}
       
   324 
       
   325 						y = this->gd_top + x_axis_offset - (x_axis_offset * datapoint) / (highest_value >> reduce_range);
       
   326 
       
   327 						/* Draw the point. */
       
   328 						GfxFillRect(x - 1, y - 1, x + 1, y + 1, color);
       
   329 
       
   330 						/* Draw the line connected to the previous point. */
       
   331 						if (prev_x != INVALID_DATAPOINT_POS) GfxDrawLine(prev_x, prev_y, x, y, color);
       
   332 
       
   333 						prev_x = x;
       
   334 						prev_y = y;
       
   335 					} else {
       
   336 						prev_x = INVALID_DATAPOINT_POS;
       
   337 						prev_y = INVALID_DATAPOINT_POS;
       
   338 					}
       
   339 
       
   340 					x += GRAPH_X_POSITION_SEPARATION;
       
   341 				}
       
   342 			}
       
   343 		}
       
   344 	}
       
   345 
       
   346 
       
   347 	BaseGraphWindow(const WindowDesc *desc, WindowNumber window_number, int left,
       
   348 									int top, int height, bool has_negative_values, StringID format_str_y_axis) :
       
   349 			Window(desc, window_number), has_negative_values(has_negative_values),
       
   350 			gd_left(left), gd_top(top), gd_height(height), format_str_y_axis(format_str_y_axis)
       
   351 	{
       
   352 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   353 	}
       
   354 
       
   355 public:
       
   356 	virtual void OnPaint()
       
   357 	{
       
   358 		this->DrawWidgets();
       
   359 
       
   360 		uint excluded_players = _legend_excluded_players;
       
   361 
       
   362 		/* Exclude the players which aren't valid */
       
   363 		const Player* p;
       
   364 		FOR_ALL_PLAYERS(p) {
       
   365 			if (!p->is_active) SetBit(excluded_players, p->index);
       
   366 		}
       
   367 		this->excluded_data = excluded_players;
       
   368 		this->num_vert_lines = 24;
       
   369 
       
   370 		byte nums = 0;
       
   371 		FOR_ALL_PLAYERS(p) {
       
   372 			if (p->is_active) nums = max(nums, p->num_valid_stat_ent);
       
   373 		}
       
   374 		this->num_on_x_axis = min(nums, 24);
       
   375 
       
   376 		int mo = (_cur_month / 3 - nums) * 3;
       
   377 		int yr = _cur_year;
       
   378 		while (mo < 0) {
       
   379 			yr--;
       
   380 			mo += 12;
       
   381 		}
       
   382 
       
   383 		this->year = yr;
       
   384 		this->month = mo;
       
   385 
       
   386 		int numd = 0;
       
   387 		FOR_ALL_PLAYERS(p) {
       
   388 			if (p->is_active) {
       
   389 				this->colors[numd] = _colour_gradient[p->player_color][6];
       
   390 				for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
       
   391 					this->cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : GetGraphData(p, j);
       
   392 					i++;
       
   393 				}
       
   394 			}
       
   395 			numd++;
       
   396 		}
       
   397 
       
   398 		this->num_dataset = numd;
       
   399 
       
   400 		this->DrawGraph();
       
   401 	}
       
   402 
       
   403 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
       
   404 	{
       
   405 		return INVALID_DATAPOINT;
       
   406 	}
       
   407 
       
   408 	virtual void OnClick(Point pt, int widget)
       
   409 	{
       
   410 		/* Clicked on legend? */
       
   411 		if (widget == 2) ShowGraphLegend();
       
   412 	}
       
   413 };
       
   414 
   344 /********************/
   415 /********************/
   345 /* OPERATING PROFIT */
   416 /* OPERATING PROFIT */
   346 /********************/
   417 /********************/
   347 
   418 
   348 static void SetupGraphDrawerForPlayers(GraphDrawer *gd)
   419 struct OperatingProfitGraphWindow : BaseGraphWindow {
   349 {
   420 	OperatingProfitGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   350 	const Player* p;
   421 			BaseGraphWindow(desc, window_number, 2, 18, 136, true, STR_CURRCOMPACT)
   351 	uint excluded_players = _legend_excluded_players;
   422 	{
   352 	byte nums;
   423 		this->FindWindowPlacementAndResize(desc);
   353 	int mo, yr;
   424 	}
   354 
   425 
   355 	/* Exclude the players which aren't valid */
   426 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   356 	FOR_ALL_PLAYERS(p) {
   427 	{
   357 		if (!p->is_active) SetBit(excluded_players, p->index);
   428 		return p->old_economy[j].income + p->old_economy[j].expenses;
   358 	}
   429 	}
   359 	gd->excluded_data = excluded_players;
   430 };
   360 	gd->num_vert_lines = 24;
       
   361 
       
   362 	nums = 0;
       
   363 	FOR_ALL_PLAYERS(p) {
       
   364 		if (p->is_active) nums = max(nums, p->num_valid_stat_ent);
       
   365 	}
       
   366 	gd->num_on_x_axis = min(nums, 24);
       
   367 
       
   368 	mo = (_cur_month / 3 - nums) * 3;
       
   369 	yr = _cur_year;
       
   370 	while (mo < 0) {
       
   371 		yr--;
       
   372 		mo += 12;
       
   373 	}
       
   374 
       
   375 	gd->year = yr;
       
   376 	gd->month = mo;
       
   377 }
       
   378 
       
   379 static void OperatingProfitWndProc(Window *w, WindowEvent *e)
       
   380 {
       
   381 	switch (e->event) {
       
   382 		case WE_PAINT: {
       
   383 			GraphDrawer gd;
       
   384 			const Player* p;
       
   385 
       
   386 			w->DrawWidgets();
       
   387 
       
   388 			gd.left = 2;
       
   389 			gd.top = 18;
       
   390 			gd.height = 136;
       
   391 			gd.has_negative_values = true;
       
   392 			gd.format_str_y_axis = STR_CURRCOMPACT;
       
   393 
       
   394 			SetupGraphDrawerForPlayers(&gd);
       
   395 
       
   396 			int numd = 0;
       
   397 			FOR_ALL_PLAYERS(p) {
       
   398 				if (p->is_active) {
       
   399 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   400 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   401 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : (p->old_economy[j].income + p->old_economy[j].expenses);
       
   402 						i++;
       
   403 					}
       
   404 				}
       
   405 				numd++;
       
   406 			}
       
   407 
       
   408 			gd.num_dataset = numd;
       
   409 
       
   410 			DrawGraph(&gd);
       
   411 			break;
       
   412 		}
       
   413 
       
   414 		case WE_CLICK:
       
   415 			/* Clicked on legend? */
       
   416 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   417 			break;
       
   418 	}
       
   419 }
       
   420 
   431 
   421 static const Widget _operating_profit_widgets[] = {
   432 static const Widget _operating_profit_widgets[] = {
   422 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                        STR_018B_CLOSE_WINDOW},
   433 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                        STR_018B_CLOSE_WINDOW},
   423 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7025_OPERATING_PROFIT_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   434 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7025_OPERATING_PROFIT_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   424 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                    STR_704D_SHOW_KEY_TO_GRAPHS},
   435 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                    STR_704D_SHOW_KEY_TO_GRAPHS},
   429 static const WindowDesc _operating_profit_desc = {
   440 static const WindowDesc _operating_profit_desc = {
   430 	WDP_AUTO, WDP_AUTO, 576, 174, 576, 174,
   441 	WDP_AUTO, WDP_AUTO, 576, 174, 576, 174,
   431 	WC_OPERATING_PROFIT, WC_NONE,
   442 	WC_OPERATING_PROFIT, WC_NONE,
   432 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   443 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   433 	_operating_profit_widgets,
   444 	_operating_profit_widgets,
   434 	OperatingProfitWndProc
   445 	NULL
   435 };
   446 };
   436 
   447 
   437 
   448 
   438 void ShowOperatingProfitGraph()
   449 void ShowOperatingProfitGraph()
   439 {
   450 {
   440 	if (AllocateWindowDescFront<Window>(&_operating_profit_desc, 0)) {
   451 	AllocateWindowDescFront<OperatingProfitGraphWindow>(&_operating_profit_desc, 0);
   441 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   442 	}
       
   443 }
   452 }
   444 
   453 
   445 
   454 
   446 /****************/
   455 /****************/
   447 /* INCOME GRAPH */
   456 /* INCOME GRAPH */
   448 /****************/
   457 /****************/
   449 
   458 
   450 static void IncomeGraphWndProc(Window *w, WindowEvent *e)
   459 struct IncomeGraphWindow : BaseGraphWindow {
   451 {
   460 	IncomeGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   452 	switch (e->event) {
   461 			BaseGraphWindow(desc, window_number, 2, 18, 104, false, STR_CURRCOMPACT)
   453 		case WE_PAINT: {
   462 	{
   454 			GraphDrawer gd;
   463 		this->FindWindowPlacementAndResize(desc);
   455 			const Player* p;
   464 	}
   456 
   465 
   457 			w->DrawWidgets();
   466 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   458 
   467 	{
   459 			gd.left = 2;
   468 		return p->old_economy[j].income;
   460 			gd.top = 18;
   469 	}
   461 			gd.height = 104;
   470 };
   462 			gd.has_negative_values = false;
       
   463 			gd.format_str_y_axis = STR_CURRCOMPACT;
       
   464 			SetupGraphDrawerForPlayers(&gd);
       
   465 
       
   466 			int numd = 0;
       
   467 			FOR_ALL_PLAYERS(p) {
       
   468 				if (p->is_active) {
       
   469 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   470 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   471 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : p->old_economy[j].income;
       
   472 						i++;
       
   473 					}
       
   474 				}
       
   475 				numd++;
       
   476 			}
       
   477 
       
   478 			gd.num_dataset = numd;
       
   479 
       
   480 			DrawGraph(&gd);
       
   481 			break;
       
   482 		}
       
   483 
       
   484 		case WE_CLICK:
       
   485 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   486 			break;
       
   487 	}
       
   488 }
       
   489 
   471 
   490 static const Widget _income_graph_widgets[] = {
   472 static const Widget _income_graph_widgets[] = {
   491 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
   473 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,              STR_018B_CLOSE_WINDOW},
   492 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7022_INCOME_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   474 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7022_INCOME_GRAPH, STR_018C_WINDOW_TITLE_DRAG_THIS},
   493 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,          STR_704D_SHOW_KEY_TO_GRAPHS},
   475 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,          STR_704D_SHOW_KEY_TO_GRAPHS},
   498 static const WindowDesc _income_graph_desc = {
   480 static const WindowDesc _income_graph_desc = {
   499 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   481 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   500 	WC_INCOME_GRAPH, WC_NONE,
   482 	WC_INCOME_GRAPH, WC_NONE,
   501 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   483 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   502 	_income_graph_widgets,
   484 	_income_graph_widgets,
   503 	IncomeGraphWndProc
   485 	NULL
   504 };
   486 };
   505 
   487 
   506 void ShowIncomeGraph()
   488 void ShowIncomeGraph()
   507 {
   489 {
   508 	if (AllocateWindowDescFront<Window>(&_income_graph_desc, 0)) {
   490 	AllocateWindowDescFront<IncomeGraphWindow>(&_income_graph_desc, 0);
   509 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   510 	}
       
   511 }
   491 }
   512 
   492 
   513 /*******************/
   493 /*******************/
   514 /* DELIVERED CARGO */
   494 /* DELIVERED CARGO */
   515 /*******************/
   495 /*******************/
   516 
   496 
   517 static void DeliveredCargoGraphWndProc(Window *w, WindowEvent *e)
   497 struct DeliveredCargoGraphWindow : BaseGraphWindow {
   518 {
   498 	DeliveredCargoGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   519 	switch (e->event) {
   499 			BaseGraphWindow(desc, window_number, 2, 18, 104, false, STR_7024)
   520 		case WE_PAINT: {
   500 	{
   521 			GraphDrawer gd;
   501 		this->FindWindowPlacementAndResize(desc);
   522 			const Player* p;
   502 	}
   523 
   503 
   524 			w->DrawWidgets();
   504 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   525 
   505 	{
   526 			gd.left = 2;
   506 		return p->old_economy[j].delivered_cargo;
   527 			gd.top = 18;
   507 	}
   528 			gd.height = 104;
   508 };
   529 			gd.has_negative_values = false;
       
   530 			gd.format_str_y_axis = STR_7024;
       
   531 			SetupGraphDrawerForPlayers(&gd);
       
   532 
       
   533 			int numd = 0;
       
   534 			FOR_ALL_PLAYERS(p) {
       
   535 				if (p->is_active) {
       
   536 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   537 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   538 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : (OverflowSafeInt64)p->old_economy[j].delivered_cargo;
       
   539 						i++;
       
   540 					}
       
   541 				}
       
   542 				numd++;
       
   543 			}
       
   544 
       
   545 			gd.num_dataset = numd;
       
   546 
       
   547 			DrawGraph(&gd);
       
   548 			break;
       
   549 		}
       
   550 
       
   551 		case WE_CLICK:
       
   552 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   553 			break;
       
   554 	}
       
   555 }
       
   556 
   509 
   557 static const Widget _delivered_cargo_graph_widgets[] = {
   510 static const Widget _delivered_cargo_graph_widgets[] = {
   558 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
   511 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
   559 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7050_UNITS_OF_CARGO_DELIVERED, STR_018C_WINDOW_TITLE_DRAG_THIS},
   512 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7050_UNITS_OF_CARGO_DELIVERED, STR_018C_WINDOW_TITLE_DRAG_THIS},
   560 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                      STR_704D_SHOW_KEY_TO_GRAPHS},
   513 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                      STR_704D_SHOW_KEY_TO_GRAPHS},
   565 static const WindowDesc _delivered_cargo_graph_desc = {
   518 static const WindowDesc _delivered_cargo_graph_desc = {
   566 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   519 	WDP_AUTO, WDP_AUTO, 576, 142, 576, 142,
   567 	WC_DELIVERED_CARGO, WC_NONE,
   520 	WC_DELIVERED_CARGO, WC_NONE,
   568 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   521 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   569 	_delivered_cargo_graph_widgets,
   522 	_delivered_cargo_graph_widgets,
   570 	DeliveredCargoGraphWndProc
   523 	NULL
   571 };
   524 };
   572 
   525 
   573 void ShowDeliveredCargoGraph()
   526 void ShowDeliveredCargoGraph()
   574 {
   527 {
   575 	if (AllocateWindowDescFront<Window>(&_delivered_cargo_graph_desc, 0)) {
   528 	AllocateWindowDescFront<DeliveredCargoGraphWindow>(&_delivered_cargo_graph_desc, 0);
   576 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   577 	}
       
   578 }
   529 }
   579 
   530 
   580 /***********************/
   531 /***********************/
   581 /* PERFORMANCE HISTORY */
   532 /* PERFORMANCE HISTORY */
   582 /***********************/
   533 /***********************/
   583 
   534 
   584 static void PerformanceHistoryWndProc(Window *w, WindowEvent *e)
   535 struct PerformanceHistoryGraphWindow : BaseGraphWindow {
   585 {
   536 	PerformanceHistoryGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   586 	switch (e->event) {
   537 			BaseGraphWindow(desc, window_number, 2, 18, 200, false, STR_7024)
   587 		case WE_PAINT: {
   538 	{
   588 			GraphDrawer gd;
   539 		this->FindWindowPlacementAndResize(desc);
   589 			const Player* p;
   540 	}
   590 
   541 
   591 			w->DrawWidgets();
   542 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   592 
   543 	{
   593 			gd.left = 2;
   544 		return p->old_economy[j].performance_history;
   594 			gd.top = 18;
   545 	}
   595 			gd.height = 200;
   546 
   596 			gd.has_negative_values = false;
   547 	virtual void OnClick(Point pt, int widget)
   597 			gd.format_str_y_axis = STR_7024;
   548 	{
   598 			SetupGraphDrawerForPlayers(&gd);
   549 		if (widget == 3) ShowPerformanceRatingDetail();
   599 
   550 		this->BaseGraphWindow::OnClick(pt, widget);
   600 			int numd = 0;
   551 	}
   601 			FOR_ALL_PLAYERS(p) {
   552 };
   602 				if (p->is_active) {
       
   603 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   604 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   605 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : (OverflowSafeInt64)p->old_economy[j].performance_history;
       
   606 						i++;
       
   607 					}
       
   608 				}
       
   609 				numd++;
       
   610 			}
       
   611 
       
   612 			gd.num_dataset = numd;
       
   613 
       
   614 			DrawGraph(&gd);
       
   615 			break;
       
   616 		}
       
   617 
       
   618 		case WE_CLICK:
       
   619 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   620 			if (e->we.click.widget == 3) ShowPerformanceRatingDetail();
       
   621 			break;
       
   622 	}
       
   623 }
       
   624 
   553 
   625 static const Widget _performance_history_widgets[] = {
   554 static const Widget _performance_history_widgets[] = {
   626 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                             STR_018B_CLOSE_WINDOW},
   555 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                             STR_018B_CLOSE_WINDOW},
   627 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   475,     0,    13, STR_7051_COMPANY_PERFORMANCE_RATINGS, STR_018C_WINDOW_TITLE_DRAG_THIS},
   556 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   475,     0,    13, STR_7051_COMPANY_PERFORMANCE_RATINGS, STR_018C_WINDOW_TITLE_DRAG_THIS},
   628 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                         STR_704D_SHOW_KEY_TO_GRAPHS},
   557 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,                         STR_704D_SHOW_KEY_TO_GRAPHS},
   634 static const WindowDesc _performance_history_desc = {
   563 static const WindowDesc _performance_history_desc = {
   635 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   564 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   636 	WC_PERFORMANCE_HISTORY, WC_NONE,
   565 	WC_PERFORMANCE_HISTORY, WC_NONE,
   637 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   566 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   638 	_performance_history_widgets,
   567 	_performance_history_widgets,
   639 	PerformanceHistoryWndProc
   568 	NULL
   640 };
   569 };
   641 
   570 
   642 void ShowPerformanceHistoryGraph()
   571 void ShowPerformanceHistoryGraph()
   643 {
   572 {
   644 	if (AllocateWindowDescFront<Window>(&_performance_history_desc, 0)) {
   573 	AllocateWindowDescFront<PerformanceHistoryGraphWindow>(&_performance_history_desc, 0);
   645 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   646 	}
       
   647 }
   574 }
   648 
   575 
   649 /*****************/
   576 /*****************/
   650 /* COMPANY VALUE */
   577 /* COMPANY VALUE */
   651 /*****************/
   578 /*****************/
   652 
   579 
   653 static void CompanyValueGraphWndProc(Window *w, WindowEvent *e)
   580 struct CompanyValueGraphWindow : BaseGraphWindow {
   654 {
   581 	CompanyValueGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   655 	switch (e->event) {
   582 			BaseGraphWindow(desc, window_number, 2, 18, 200, false, STR_CURRCOMPACT)
   656 		case WE_PAINT: {
   583 	{
   657 			GraphDrawer gd;
   584 		this->FindWindowPlacementAndResize(desc);
   658 			const Player* p;
   585 	}
   659 
   586 
   660 			w->DrawWidgets();
   587 	virtual OverflowSafeInt64 GetGraphData(const Player *p, int j)
   661 
   588 	{
   662 			gd.left = 2;
   589 		return p->old_economy[j].company_value;
   663 			gd.top = 18;
   590 	}
   664 			gd.height = 200;
   591 };
   665 			gd.has_negative_values = false;
       
   666 			gd.format_str_y_axis = STR_CURRCOMPACT;
       
   667 			SetupGraphDrawerForPlayers(&gd);
       
   668 
       
   669 			int numd = 0;
       
   670 			FOR_ALL_PLAYERS(p) {
       
   671 				if (p->is_active) {
       
   672 					gd.colors[numd] = _colour_gradient[p->player_color][6];
       
   673 					for (int j = gd.num_on_x_axis, i = 0; --j >= 0;) {
       
   674 						gd.cost[numd][i] = (j >= p->num_valid_stat_ent) ? INVALID_DATAPOINT : p->old_economy[j].company_value;
       
   675 						i++;
       
   676 					}
       
   677 				}
       
   678 				numd++;
       
   679 			}
       
   680 
       
   681 			gd.num_dataset = numd;
       
   682 
       
   683 			DrawGraph(&gd);
       
   684 			break;
       
   685 		}
       
   686 
       
   687 		case WE_CLICK:
       
   688 			if (e->we.click.widget == 2) ShowGraphLegend();
       
   689 			break;
       
   690 	}
       
   691 }
       
   692 
   592 
   693 static const Widget _company_value_graph_widgets[] = {
   593 static const Widget _company_value_graph_widgets[] = {
   694 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                STR_018B_CLOSE_WINDOW},
   594 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                STR_018B_CLOSE_WINDOW},
   695 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7052_COMPANY_VALUES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   595 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   525,     0,    13, STR_7052_COMPANY_VALUES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   696 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,            STR_704D_SHOW_KEY_TO_GRAPHS},
   596 { WWT_PUSHTXTBTN,   RESIZE_NONE,    14,   526,   575,     0,    13, STR_704C_KEY,            STR_704D_SHOW_KEY_TO_GRAPHS},
   701 static const WindowDesc _company_value_graph_desc = {
   601 static const WindowDesc _company_value_graph_desc = {
   702 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   602 	WDP_AUTO, WDP_AUTO, 576, 238, 576, 238,
   703 	WC_COMPANY_VALUE, WC_NONE,
   603 	WC_COMPANY_VALUE, WC_NONE,
   704 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   604 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
   705 	_company_value_graph_widgets,
   605 	_company_value_graph_widgets,
   706 	CompanyValueGraphWndProc
   606 	NULL
   707 };
   607 };
   708 
   608 
   709 void ShowCompanyValueGraph()
   609 void ShowCompanyValueGraph()
   710 {
   610 {
   711 	if (AllocateWindowDescFront<Window>(&_company_value_graph_desc, 0)) {
   611 	AllocateWindowDescFront<CompanyValueGraphWindow>(&_company_value_graph_desc, 0);
   712 		InvalidateWindow(WC_GRAPH_LEGEND, 0);
       
   713 	}
       
   714 }
   612 }
   715 
   613 
   716 /*****************/
   614 /*****************/
   717 /* PAYMENT RATES */
   615 /* PAYMENT RATES */
   718 /*****************/
   616 /*****************/
   719 
   617 
   720 static void CargoPaymentRatesWndProc(Window *w, WindowEvent *e)
   618 struct PaymentRatesGraphWindow : BaseGraphWindow {
   721 {
   619 	PaymentRatesGraphWindow(const WindowDesc *desc, WindowNumber window_number) :
   722 	switch (e->event) {
   620 			BaseGraphWindow(desc, window_number, 2, 24, 200, false, STR_CURRCOMPACT)
   723 		case WE_PAINT: {
   621 	{
   724 			GraphDrawer gd;
   622 		uint num_active = 0;
   725 
   623 		for (CargoID c = 0; c < NUM_CARGO; c++) {
   726 			w->DrawWidgets();
   624 			if (GetCargo(c)->IsValid()) num_active++;
   727 
   625 		}
   728 			int x = 495;
   626 
   729 			int y = 24;
   627 		/* Resize the window to fit the cargo types */
   730 
   628 		ResizeWindow(this, 0, max(num_active, 12U) * 8);
   731 			gd.excluded_data = _legend_excluded_cargo;
   629 
   732 			gd.left = 2;
   630 		/* Add widgets for each cargo type */
   733 			gd.top = 24;
   631 		this->widget_count += num_active;
   734 			gd.height = w->height - 38;
   632 		this->widget = ReallocT(this->widget, this->widget_count + 1);
   735 			gd.has_negative_values = false;
   633 		this->widget[this->widget_count].type = WWT_LAST;
   736 			gd.format_str_y_axis = STR_CURRCOMPACT;
   634 
   737 			gd.num_on_x_axis = 20;
   635 		/* Set the properties of each widget */
   738 			gd.num_vert_lines = 20;
   636 		for (uint i = 0; i != num_active; i++) {
   739 			gd.month = 0xFF;
   637 			Widget *wi = &this->widget[3 + i];
   740 			gd.x_values_start     = 10;
   638 			wi->type     = WWT_PANEL;
   741 			gd.x_values_increment = 10;
   639 			wi->display_flags = RESIZE_NONE;
   742 
   640 			wi->color    = 12;
   743 			uint i = 0;
   641 			wi->left     = 493;
   744 			for (CargoID c = 0; c < NUM_CARGO; c++) {
   642 			wi->right    = 562;
   745 				const CargoSpec *cs = GetCargo(c);
   643 			wi->top      = 24 + i * 8;
   746 				if (!cs->IsValid()) continue;
   644 			wi->bottom   = wi->top + 7;
   747 
   645 			wi->data     = 0;
   748 				/* Only draw labels for widgets that exist. If the widget doesn't
   646 			wi->tooltips = STR_7064_TOGGLE_GRAPH_FOR_CARGO;
   749 				 * exist then the local player has used the climate cheat or
   647 
   750 				 * changed the NewGRF configuration with this window open. */
   648 			if (!HasBit(_legend_excluded_cargo, i)) this->LowerWidget(i + 3);
   751 				if (i + 3 < w->widget_count) {
   649 		}
   752 					/* Since the buttons have no text, no images,
   650 
   753 					 * both the text and the colored box have to be manually painted.
   651 		this->SetDirty();
   754 					 * clk_dif will move one pixel down and one pixel to the right
   652 
   755 					 * when the button is clicked */
   653 		this->gd_height = this->height - 38;
   756 					byte clk_dif = w->IsWidgetLowered(i + 3) ? 1 : 0;
   654 		this->num_on_x_axis = 20;
   757 
   655 		this->num_vert_lines = 20;
   758 					GfxFillRect(x + clk_dif, y + clk_dif, x + 8 + clk_dif, y + 5 + clk_dif, 0);
   656 		this->month = 0xFF;
   759 					GfxFillRect(x + 1 + clk_dif, y + 1 + clk_dif, x + 7 + clk_dif, y + 4 + clk_dif, cs->legend_colour);
   657 		this->x_values_start     = 10;
   760 					SetDParam(0, cs->name);
   658 		this->x_values_increment = 10;
   761 					DrawString(x + 14 + clk_dif, y + clk_dif, STR_7065, TC_FROMSTRING);
   659 
   762 					y += 8;
   660 		this->FindWindowPlacementAndResize(desc);
   763 				}
   661 	}
   764 
   662 
   765 				gd.colors[i] = cs->legend_colour;
   663 	virtual void OnPaint()
   766 				for (uint j = 0; j != 20; j++) {
   664 	{
   767 					gd.cost[i][j] = GetTransportedGoodsIncome(10, 20, j * 6 + 6, c);
   665 		this->DrawWidgets();
   768 				}
   666 
   769 
   667 		this->excluded_data = _legend_excluded_cargo;
   770 				i++;
   668 
   771 			}
   669 		int x = 495;
   772 			gd.num_dataset = i;
   670 		int y = 24;
   773 
   671 
   774 			DrawGraph(&gd);
   672 		uint i = 0;
   775 
   673 		for (CargoID c = 0; c < NUM_CARGO; c++) {
   776 			DrawString(2 + 46, 24 + gd.height + 7, STR_7062_DAYS_IN_TRANSIT, TC_FROMSTRING);
   674 			const CargoSpec *cs = GetCargo(c);
   777 			DrawString(2 + 84, 24 - 9, STR_7063_PAYMENT_FOR_DELIVERING, TC_FROMSTRING);
   675 			if (!cs->IsValid()) continue;
   778 			break;
   676 
   779 		}
   677 			/* Only draw labels for widgets that exist. If the widget doesn't
   780 
   678 				* exist then the local player has used the climate cheat or
   781 		case WE_CLICK:
   679 				* changed the NewGRF configuration with this window open. */
   782 			if (e->we.click.widget >= 3) {
   680 			if (i + 3 < this->widget_count) {
   783 				ToggleBit(_legend_excluded_cargo, e->we.click.widget - 3);
   681 				/* Since the buttons have no text, no images,
   784 				w->ToggleWidgetLoweredState(e->we.click.widget);
   682 					* both the text and the colored box have to be manually painted.
   785 				w->SetDirty();
   683 					* clk_dif will move one pixel down and one pixel to the right
   786 			}
   684 					* when the button is clicked */
   787 			break;
   685 				byte clk_dif = this->IsWidgetLowered(i + 3) ? 1 : 0;
   788 	}
   686 
   789 }
   687 				GfxFillRect(x + clk_dif, y + clk_dif, x + 8 + clk_dif, y + 5 + clk_dif, 0);
       
   688 				GfxFillRect(x + 1 + clk_dif, y + 1 + clk_dif, x + 7 + clk_dif, y + 4 + clk_dif, cs->legend_colour);
       
   689 				SetDParam(0, cs->name);
       
   690 				DrawString(x + 14 + clk_dif, y + clk_dif, STR_7065, TC_FROMSTRING);
       
   691 				y += 8;
       
   692 			}
       
   693 
       
   694 			this->colors[i] = cs->legend_colour;
       
   695 			for (uint j = 0; j != 20; j++) {
       
   696 				this->cost[i][j] = GetTransportedGoodsIncome(10, 20, j * 6 + 6, c);
       
   697 			}
       
   698 
       
   699 			i++;
       
   700 		}
       
   701 		this->num_dataset = i;
       
   702 
       
   703 		this->DrawGraph();
       
   704 
       
   705 		DrawString(2 + 46, 24 + this->gd_height + 7, STR_7062_DAYS_IN_TRANSIT, TC_FROMSTRING);
       
   706 		DrawString(2 + 84, 24 - 9, STR_7063_PAYMENT_FOR_DELIVERING, TC_FROMSTRING);
       
   707 	}
       
   708 
       
   709 	virtual void OnClick(Point pt, int widget)
       
   710 	{
       
   711 		if (widget >= 3) {
       
   712 			ToggleBit(_legend_excluded_cargo, widget - 3);
       
   713 			this->ToggleWidgetLoweredState(widget);
       
   714 			this->SetDirty();
       
   715 		}
       
   716 	}
       
   717 };
   790 
   718 
   791 static const Widget _cargo_payment_rates_widgets[] = {
   719 static const Widget _cargo_payment_rates_widgets[] = {
   792 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                     STR_018B_CLOSE_WINDOW},
   720 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                     STR_018B_CLOSE_WINDOW},
   793 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   567,     0,    13, STR_7061_CARGO_PAYMENT_RATES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   721 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   567,     0,    13, STR_7061_CARGO_PAYMENT_RATES, STR_018C_WINDOW_TITLE_DRAG_THIS},
   794 {      WWT_PANEL, RESIZE_BOTTOM,    14,     0,   567,    14,    45, 0x0,                          STR_NULL},
   722 {      WWT_PANEL, RESIZE_BOTTOM,    14,     0,   567,    14,    45, 0x0,                          STR_NULL},
   798 static const WindowDesc _cargo_payment_rates_desc = {
   726 static const WindowDesc _cargo_payment_rates_desc = {
   799 	WDP_AUTO, WDP_AUTO, 568, 46, 568, 46,
   727 	WDP_AUTO, WDP_AUTO, 568, 46, 568, 46,
   800 	WC_PAYMENT_RATES, WC_NONE,
   728 	WC_PAYMENT_RATES, WC_NONE,
   801 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
   729 	WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET,
   802 	_cargo_payment_rates_widgets,
   730 	_cargo_payment_rates_widgets,
   803 	CargoPaymentRatesWndProc
   731 	NULL
   804 };
   732 };
   805 
   733 
   806 
   734 
   807 void ShowCargoPaymentRates()
   735 void ShowCargoPaymentRates()
   808 {
   736 {
   809 	Window *w = AllocateWindowDescFront<Window>(&_cargo_payment_rates_desc, 0);
   737 	AllocateWindowDescFront<PaymentRatesGraphWindow>(&_cargo_payment_rates_desc, 0);
   810 	if (w == NULL) return;
       
   811 
       
   812 	/* Count the number of active cargo types */
       
   813 	uint num_active = 0;
       
   814 	for (CargoID c = 0; c < NUM_CARGO; c++) {
       
   815 		if (GetCargo(c)->IsValid()) num_active++;
       
   816 	}
       
   817 
       
   818 	/* Resize the window to fit the cargo types */
       
   819 	ResizeWindow(w, 0, max(num_active, 12U) * 8);
       
   820 
       
   821 	/* Add widgets for each cargo type */
       
   822 	w->widget_count += num_active;
       
   823 	w->widget = ReallocT(w->widget, w->widget_count + 1);
       
   824 	w->widget[w->widget_count].type = WWT_LAST;
       
   825 
       
   826 	/* Set the properties of each widget */
       
   827 	for (uint i = 0; i != num_active; i++) {
       
   828 		Widget *wi = &w->widget[3 + i];
       
   829 		wi->type     = WWT_PANEL;
       
   830 		wi->display_flags = RESIZE_NONE;
       
   831 		wi->color    = 12;
       
   832 		wi->left     = 493;
       
   833 		wi->right    = 562;
       
   834 		wi->top      = 24 + i * 8;
       
   835 		wi->bottom   = wi->top + 7;
       
   836 		wi->data     = 0;
       
   837 		wi->tooltips = STR_7064_TOGGLE_GRAPH_FOR_CARGO;
       
   838 
       
   839 		if (!HasBit(_legend_excluded_cargo, i)) w->LowerWidget(i + 3);
       
   840 	}
       
   841 
       
   842 	w->SetDirty();
       
   843 }
   738 }
   844 
   739 
   845 /************************/
   740 /************************/
   846 /* COMPANY LEAGUE TABLE */
   741 /* COMPANY LEAGUE TABLE */
   847 /************************/
   742 /************************/
   946 		}
   841 		}
   947 
   842 
   948 		this->UpdatePlayerStats();
   843 		this->UpdatePlayerStats();
   949 
   844 
   950 		if (player != INVALID_PLAYER) this->LowerWidget(player + 13);
   845 		if (player != INVALID_PLAYER) this->LowerWidget(player + 13);
   951 		this->SetDirty();
   846 
       
   847 		this->FindWindowPlacementAndResize(desc);
   952 	}
   848 	}
   953 
   849 
   954 	void UpdatePlayerStats()
   850 	void UpdatePlayerStats()
   955 	{
   851 	{
   956 		/* Update all player stats with the current data
   852 		/* Update all player stats with the current data