PPP2 Chapter 15 Exercise 6

Pages: 12
m_values[i] * graph.height / maxValue is the formula to use. To see this, lets supposed the bar graph is showing the weight in kilograms of different people. The tallest bar represents the heaviest person (maxValue) and is graph.height pixels tall. This establishes the scale. There are graph.height/maxValue pixels per kg. To get the height of any other bar, just multiply the value (kg) times the scale (pixels/kg) to get the height in pixels.
I may need some additional help. Some people who have read the book weighing in some more would be appreciated as well because I also want to how to color the individual rectangles representing the bars.

First, though, I just need to get the math for positioning the rectangles right. I have to maximize the window to be able to see the whole thing as it is now.

Here's my current code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Osman Zakir
// 7 / 4 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 15 Exercise 6
// chapter15ex6.cpp
// Exercise Specifications:
/**
* Design and implement a bar graph class. Its basic data is a vector<double>
* holding N values, and each value should be represented by a “bar” that is
* a rectangle where the height represents the value.
*/

#include <vector>
#include <iostream>
#include <algorithm>
#include "../../Graph.h"
#include "../../Window.h"

namespace Graph_lib
{
	struct Bar_graph : Shape
	{
		Bar_graph(const int graph_width, const int graph_height, const std::vector<double> &values);
		void draw_lines() const;
		int graph_width() const { return m_graph_width; }
		int graph_height() const { return m_graph_height; }
		const std::vector<double> &values_v() const { return m_values; }
		const Vector_ref<Rectangle> &bars_v() const { return m_bars_v; }
		void set_graph_width(const int value) { m_graph_width = value; }
		void set_graph_height(const int value) { m_graph_height = value; }
	private:
		int m_graph_width;	// width of whole graph
		int m_graph_height; // height of whole graph
		std::vector<double> m_values;
		Vector_ref<Rectangle> m_bars_v;
	};
}

int main()
{
	using namespace std;
	using namespace Graph_lib;
	try
	{
		constexpr int win_x = 100;
		constexpr int win_y = 100;
		constexpr int win_width = 600;
		constexpr int win_height = 400;
		const Point win_tl{ win_x, win_y };
		Graph_lib::Window win{ win_tl, win_width, win_height, "Bar graphs" };

		constexpr int graph_width = win_width - 40;
		constexpr int graph_height = win_height - 60;
		vector<double> values{ 5.6, 5.6, 4.3, 5.0 };
		Bar_graph bar_graph{ graph_width, graph_height, values };
		bar_graph.set_color(Color::black);
		bar_graph.set_fill_color(Color::blue);

		win.attach(bar_graph);

		gui_main();
	}
	catch (const runtime_error &e)
	{
		constexpr int win_x = 100;
		constexpr int win_y = 100;
		constexpr int win_width = 600;
		constexpr int win_height = 400;
		const Point win_tl{ win_x, win_y };
		Graph_lib::Window win{ win_tl, win_width, win_height, "Bar graphs" };

		Text err_msg_start{ Point{(win_width / 2) - 200, win_height / 2}, "Runtime error: " };
		Text err_msg{ Point{(win_width / 2) + err_msg_start.label().length() + 5, win_height / 2}, e.what() };
		err_msg_start.set_color(Color::black);
		err_msg.set_color(Color::black);

		win.attach(err_msg_start);
		win.attach(err_msg);

		gui_main();
	}
}

Graph_lib::Bar_graph::Bar_graph(const int graph_width, const int graph_height, const std::vector<double> &values)
	:m_graph_width{ graph_width }, m_graph_height{ graph_height }, m_values{ values }
{
	const std::size_t N = values.size();
	if (N == 0)
	{
		error("invalid number of values!");
	}
	int bar_width = (graph_width / N);
	double bar_spaces = bar_width * 1.0 / (2 * N - 1);
	bar_width -= bar_spaces;
	auto it = std::max_element(values.begin(), values.end());
	int max_value = *it;
	for (std::size_t i = 0; i < N; ++i)
	{
		int bar_height = std::round(values[i]) * (graph_height / max_value);
		m_bars_v.push_back(new Rectangle{ 
			Point{ (i * bar_width) + (bar_spaces * 2), bar_height }, bar_width, bar_height 
		});
	}
}

void Graph_lib::Bar_graph::draw_lines() const
{
	for (std::size_t i = 0; i < m_bars_v.size(); ++i)
	{
		m_bars_v[i].draw();
	}
}


Any help would be appreciated. Thanks in advance.
> I need to know how to put a bounding rectangle on the window and set the bars inside it.
> Right now, I even need to maximize the window to be able to see the bars, and one of them is
> in the air by some number of pixels. No space between the bars either

Something along these lines, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <iomanip>

struct rect { std::size_t width = 0 ; std::size_t height = 0 ; };

std::size_t bar_width( const std::vector<double>& data, rect screen )
{
    // // data.size() bars, data.size()+1 spacing on left, right, between bars
    const std::size_t n = data.size() * 2 + 1 ;
    if( data.empty() || n > screen.width ) return 0 ; // can't handle this
    else return screen.width / n ;
}

std::vector<std::size_t> bar_heights( const std::vector<double>& data, rect screen )
{
    constexpr std::size_t margin_size = 2 ; // min height, top margin
    if( screen.height < margin_size * 4 || data.empty() ) return {} ; // can't handle this
    const std::size_t available = screen.height - margin_size * 2 ; // available height

    const auto min_max = std::minmax_element( data.begin(), data.end() ) ;
    const double span = *min_max.second - *min_max.first ; // range of values
    if( span < 0.1 ) return {} ; // can't handle this

    std::vector<std::size_t> heights ;
    const double multiplier = available / span ;
    for( double v : data )
    {
        const double scaled_height = ( v - *min_max.first ) * multiplier ;
        heights.push_back( std::round(scaled_height) + margin_size ) ;
    }
    return heights ;
}

int main()
{
    const rect screen { 100, 100 } ;
    std::vector<double> data { 7.8, -3.2, 0.0, 5.4, -1.6, 7.9, 5.0 } ;

    const std::size_t bwidth = bar_width( data, screen ) ;
    std::cout << "width of each bar: " << bwidth << '\n' ;

    std::vector<std::size_t> bheights = bar_heights( data, screen ) ;
    std::cout << "top_left, bottom_right for each bar\n"
                 "(with bottom left of screen as (0,0):"
                 "\n---------------------------------------\n" ;
    for( std::size_t i = 0 ; i < bheights.size() ; ++i )
    {
        std::cout << std::setw(6) << data[i] << ": (" << (i*2+1)*bwidth
                  << ',' << bheights[i] << "), (" << (i*2+2)*bwidth << ",0)\n" ;
    }
}

http://coliru.stacked-crooked.com/a/7bb70faebee6d046
What about the GUI window and stuff? I want to see GUI code from the book used here so I can see what I did wrong. If it's not too much trouble.
> What about the GUI window and stuff?

Once we have the rectangle that has the dimensions of the printable area on the window, and we then compute the corners of each bar within that rectangle, rendering the bar graph is just a matter of trivial (and somewhat tedious) detail.


> I want to see GUI code from the book used here

You would have to write that part of the code yourself; it is not something that interests me in the least.
What about putting the bars inside that rectangle? I want to attach a Rectangle to the window and put the bars inside it, but I can't figure out how to do it.
> What about putting the bars inside that rectangle?

The bars are already inside that rectangle.
The x co-ordinates of each bar are in [0,rectangle.width)
The y co-ordinates of each bar are in [0,rectangle.height)
In my code they aren't.

Should I maybe start from scratch and just put the Rectangle in first, and then try to put the bars inside it?
> Rectangle in first, and then try to put the bars inside it?

That is what I had done.

Note that both bar_width() and bar_heights() take the rectangle as an argument.
Then lines 51 and 52 in [/tt]main()[/tt] place the bars inside the rectangle.
Last edited on
How do I get the bars to appear inside the 600x400 area of the window, so that I don't have to maximize it to see the whole thing? I'd actually like it if I didn't need to actually attach a bounding Rectangle (as in, a Graph_lib::Rectangle) outside the bars.

Here's my current code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Osman Zakir
// 7 / 4 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 15 Exercise 6
// chapter15ex6.cpp
// Exercise Specifications:
/**
* Design and implement a bar graph class. Its basic data is a vector<double>
* holding N values, and each value should be represented by a “bar” that is
* a rectangle where the height represents the value.
*/

#include <vector>
#include <iostream>
#include <algorithm>
#include "../../Graph.h"
#include "../../Window.h"

namespace Graph_lib
{
	struct Bar_graph : Shape
	{
		Bar_graph(const Point &graph_tl, const int graph_width, const int graph_height, const Color &bar_fill, 
			const Color &bar_outline, const Color &graph_outline, const Color &graph_fill, const std::vector<double> &values);
		void draw_lines() const;
		int graph_width() const { return m_graph_width; }
		int graph_height() const { return m_graph_height; }
		const std::vector<double> &values_v() const { return m_values; }
		const Vector_ref<Rectangle> &bars_v() const { return m_bars_v; }
		void set_graph_width(const int value) { m_graph_width = value; }
		void set_graph_height(const int value) { m_graph_height = value; }
	private:
		Point m_graph_tl;	// top-left of rectangle bouning graph
		int m_graph_width;	// width of rectangle bounding graph
		int m_graph_height; // height of rectangle bounding graph
		std::vector<double> m_values;
		Color m_bar_fill;
		Color m_bar_outline;
		Color m_graph_outline;
		Color m_graph_fill;
		Vector_ref<Rectangle> m_bars_v;
	};
}

int main()
{
	using namespace std;
	using namespace Graph_lib;

	constexpr int win_x = 100;
	constexpr int win_y = 100;
	constexpr int win_width = 600;
	constexpr int win_height = 400;
	const Point win_tl{ win_x, win_y };
	Graph_lib::Window win{ win_tl, win_width, win_height, "Bar graphs" };

	try
	{
		constexpr int graph_width = win_width - 40;
		constexpr int graph_height = win_height - 60;
		vector<double> values{ 5.6, 5.6, 4.3, 5.0 };
		const Point rect_tl{ 100, 100 };
		Bar_graph bar_graph{ Point{100, 100}, graph_width, graph_height, Color::blue, bar_graph.color(), 
			Color::black, Color::invisible, values };
		bar_graph.set_color(Color::black);
		bar_graph.set_fill_color(Color::blue);

		win.attach(bar_graph);

		gui_main();
	}
	catch (const runtime_error &e)
	{
		Text err_msg_start{ Point{(win_width / 2) - 200, win_height / 2}, "Runtime error: " };
		Text err_msg{ Point{(win_width / 2) + err_msg_start.label().length() + 5, win_height / 2}, e.what() };
		err_msg_start.set_color(Color::black);
		err_msg.set_color(Color::black);

		win.attach(err_msg_start);
		win.attach(err_msg);

		gui_main();
	}
}

Graph_lib::Bar_graph::Bar_graph(const Point &graph_tl, const int graph_width, const int graph_height, const Color &bar_fill,
	const Color &bar_outline, const Color &graph_outline, const Color &graph_fill, const std::vector<double> &values)
	:m_graph_tl{ graph_tl }, m_graph_width{ graph_width }, m_graph_height{ graph_height }, m_bar_fill{ bar_fill }, 
	m_bar_outline{ bar_outline }, m_graph_outline{ graph_outline }, m_graph_fill{ graph_fill }, m_values{ values}
{
	const std::size_t N = values.size();
	Rectangle bounding_rect{ graph_tl, graph_width, graph_height };
	bounding_rect.set_color(graph_outline);
	bounding_rect.set_fill_color(graph_fill);
	bounding_rect.draw();
	if (N == 0)
	{
		error("invalid number of values!");
	}
	int bar_width = (graph_width / N);
	double bar_spaces = bar_width * 1.0 / (2 * N - 1);
	bar_width -= bar_spaces;
	auto it = std::max_element(values.begin(), values.end());
	int max_value = *it;
	for (std::size_t i = 0; i < N; ++i)
	{
		int bar_height = std::round(values[i]) * (graph_height / max_value);
		m_bars_v.push_back(new Rectangle{ 
			Point{ (i * bar_width) + (bar_spaces * 2), bar_height }, bar_width, bar_height 
		});
		m_bars_v[i].set_color(bar_outline);
		m_bars_v[i].set_fill_color(bar_fill);
	}
}

void Graph_lib::Bar_graph::draw_lines() const
{
	for (std::size_t i = 0; i < m_bars_v.size(); ++i)
	{
		m_bars_v[i].draw();
	}
}
Last edited on
Topic archived. No new replies allowed.
Pages: 12