Because I like a challenge, I made a tiny Canvas class, to be used like:
int main() {
using Canvas = BasicCanvas<160, 80>;
Canvas canvas;
canvas.origin = {canvas.cols()/3, canvas.rows()/3};
canvas.axes();
canvas.plot([](double x) { return x; });
canvas.plot([](double ) { return -8; });
canvas.plot([](double x) { return 3*log(x); });
canvas.plot([](double x) { return 4*sin(x/2); });
canvas.plot([](double x) { return 24*cos(x/12); });
std::cout << canvas;
}
Which prints 
Or commenting out the origin assignment: 
Implementation
The implementation basically iterates through the positions on the x axis and plots approximate line drawing characters depending on the angle (first derivative) of the function at that point:
template <size_t Columns = 100, size_t Rows = 50>
struct BasicCanvas {
using Line = std::array<char, Columns>;
using Screen = std::array<Line, Rows>;
struct Coord { size_t x, y; };
static constexpr size_t rows() { return Rows; }
static constexpr size_t cols() { return Columns; }
Screen screen;
Coord origin;
BasicCanvas(Coord origin = {Columns/2, Rows/2}) : origin(origin) {
Line empty;
std::fill(empty.begin(), empty.end(), '.');
std::fill(screen.begin(), screen.end(), empty);
}
friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
for (auto& line : c.screen) {
os.write(line.data(), line.size()) << "\n";
}
return os;
}
Line& operator[](size_t y) { return screen.at(screen.size()-(y+1)); }
Line const& operator[](size_t y) const { return screen.at(screen.size()-(y+1)); }
char& operator[](Coord coord) { return operator[](coord.y).at(coord.x); }
char const& operator[](Coord coord) const { return operator[](coord.y).at(coord.x); }
void axes() {
for (auto& line : screen)
line.at(origin.x) = '|';
auto& y_axis = operator[](origin.y);
for (auto& cell : y_axis)
cell = '-';
y_axis.at(origin.x) = '+';
}
template <typename F>
void plot(F f, double scaleX = 1.0, double scaleY = 1.0) {
for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
auto x = (x_tick * scaleX) - origin.x;
auto y = f(x);
auto y_ = derivative(f, x, scaleX/2);
size_t y_tick = (y / scaleY) + origin.y;
if (y_tick < Rows)
operator[]({x_tick, y_tick}) = glyph(y_);
}
}
private:
template <typename F>
auto derivative(F const& f, double x, double dx = 0.01) {
return (f(x+dx)-f(x-dx))/(2*dx);
}
char glyph(double tangent) {
auto angle = atan(tangent);
while (angle < 0)
angle += 2*M_PI;
int angle_index = 2.0 * angle / atan(1);
return R"(--/||\--)"[angle_index % 8];
}
};
Full Listing
Live On Coliru
(simplified function selection):
#include <iostream>
#include <array>
#include <cmath>
template <size_t Columns = 100, size_t Rows = 50>
struct BasicCanvas {
using Line = std::array<char, Columns>;
using Screen = std::array<Line, Rows>;
struct Coord { size_t x, y; };
static constexpr size_t rows() { return Rows; }
static constexpr size_t cols() { return Columns; }
Screen screen;
Coord origin;
BasicCanvas(Coord origin = {Columns/2, Rows/2}) : origin(origin) {
Line empty;
std::fill(empty.begin(), empty.end(), ' ');
std::fill(screen.begin(), screen.end(), empty);
}
friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
for (auto& line : c.screen) {
os.write(line.data(), line.size()) << "\n";
}
return os;
}
Line& operator[](size_t y) { return screen.at(screen.size()-(y+1)); }
Line const& operator[](size_t y) const { return screen.at(screen.size()-(y+1)); }
char& operator[](Coord coord) { return operator[](coord.y).at(coord.x); }
char const& operator[](Coord coord) const { return operator[](coord.y).at(coord.x); }
void axes() {
for (auto& line : screen)
line.at(origin.x) = '|';
auto& y_axis = operator[](origin.y);
for (auto& cell : y_axis)
cell = '-';
y_axis.at(origin.x) = '+';
}
template <typename F>
void plot(F f, double scaleX = 1.0, double scaleY = 1.0) {
for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
auto x = (x_tick * scaleX) - origin.x;
auto y = f(x);
auto y_ = derivative(f, x, scaleX/2);
size_t y_tick = (y / scaleY) + origin.y;
if (y_tick < Rows)
operator[]({x_tick, y_tick}) = glyph(y_);
}
}
private:
template <typename F>
auto derivative(F const& f, double x, double dx = 0.01) {
return (f(x+dx)-f(x-dx))/(2*dx);
}
char glyph(double tangent) {
auto angle = atan(tangent);
while (angle < 0)
angle += 2*M_PI;
int angle_index = 2.0 * angle / atan(1);
return R"(--/||\--)"[angle_index % 8];
}
};
int main() {
using Canvas = BasicCanvas<60, 30>;
Canvas canvas;
//canvas.origin = {canvas.cols()/3, canvas.rows()/3};
canvas.axes();
canvas.plot([](double x) { return x; });
//canvas.plot([](double ) { return -8; });
canvas.plot([](double x) { return 3*log(x); });
canvas.plot([](double x) { return 4*sin(x/2); });
//canvas.plot([](double x) { return 24*cos(x/12); });
std::cout << canvas;
}
Prints
| /
| /
| /
| /
| / -
| / --------
| / ------
| / ----
| / ---
| /--
| --
--- --\ | /-- --\ /--
/ \ / | / \ /
/ \ |/ \ /
-----/-----\------------------/|----\------/----------------
/ \ /| \ /
/ \ //| \ /
\ / \ / | / \ /
--/ \-- --/ | \-- ---
/ |
/ |
/ |
/ |
/ |
/ |
/ |
/ |
/ |
/ |
/ |