33 #ifndef SIMPLE_SVG_HPP
34 #define SIMPLE_SVG_HPP
47 std::string attribute(std::string
const & attribute_name,
48 T
const & value, std::string
const & unit =
"")
51 ss << attribute_name <<
"=\"" << value << unit <<
"\" ";
54 std::string elemStart(std::string
const & element_name)
56 return "\t<" + element_name +
" ";
58 std::string elemEnd(std::string
const & element_name)
60 return "</" + element_name +
">\n";
62 std::string emptyElemEnd()
74 optional<T>(T
const & type)
75 : valid(
true), type(type) { }
76 optional<T>() : valid(
false), type(T()) { }
81 throw std::exception();
86 bool operator!()
const {
return !valid; }
94 Dimensions(
double width,
double height) : width(width), height(height) { }
95 Dimensions(
double combined = 0) : width(combined), height(combined) { }
102 Point(
double x = 0,
double y = 0) : x(x), y(y) { }
106 optional<Point> getMinPoint(std::vector<Point>
const & points)
109 return optional<Point>();
111 Point
min = points[0];
112 for (
unsigned i = 0; i < points.size(); ++i) {
113 if (points[i].x < min.x)
115 if (points[i].y < min.y)
118 return optional<Point>(min);
120 optional<Point> getMaxPoint(std::vector<Point>
const & points)
123 return optional<Point>();
125 Point
max = points[0];
126 for (
unsigned i = 0; i < points.size(); ++i) {
127 if (points[i].x > max.x)
129 if (points[i].y > max.y)
132 return optional<Point>(max);
138 enum Origin { TopLeft, BottomLeft, TopRight, BottomRight };
140 Layout(Dimensions
const & dimensions = Dimensions(400, 300), Origin origin = BottomLeft,
141 double scale = 1, Point
const & origin_offset = Point(0, 0))
142 : dimensions(dimensions), scale(scale),
origin(
origin), origin_offset(origin_offset) { }
143 Dimensions dimensions;
150 double translateX(
double x, Layout
const & layout)
152 if (layout.origin == Layout::BottomRight || layout.origin == Layout::TopRight)
153 return layout.dimensions.width - ((x + layout.origin_offset.x) * layout.scale);
155 return (layout.origin_offset.x + x) * layout.scale;
158 double translateY(
double y, Layout
const & layout)
160 if (layout.origin == Layout::BottomLeft || layout.origin == Layout::BottomRight)
161 return layout.dimensions.height - ((y + layout.origin_offset.y) * layout.scale);
163 return (layout.origin_offset.y + y) * layout.scale;
165 double translateScale(
double dimension, Layout
const & layout)
167 return dimension * layout.scale;
174 virtual ~Serializeable() { };
175 virtual std::string toString(Layout
const & layout)
const = 0;
178 class Color :
public Serializeable
181 enum Defaults { Transparent = -1, Aqua, Black, Blue, Brown, Cyan, Fuchsia,
182 Green, Lime, Magenta, Orange, Purple, Red, Silver, White, Yellow };
184 Color(
int r,
int g,
int b) : transparent(false),
red(r),
green(g),
blue(b) { }
185 Color(Defaults color)
190 case Aqua: assign(0, 255, 255);
break;
191 case Black: assign(0, 0, 0);
break;
192 case Blue: assign(0, 0, 255);
break;
193 case Brown: assign(165, 42, 42);
break;
194 case Cyan: assign(0, 255, 255);
break;
195 case Fuchsia: assign(255, 0, 255);
break;
196 case Green: assign(0, 128, 0);
break;
197 case Lime: assign(0, 255, 0);
break;
198 case Magenta: assign(255, 0, 255);
break;
199 case Orange: assign(255, 165, 0);
break;
200 case Purple: assign(128, 0, 128);
break;
201 case Red: assign(255, 0, 0);
break;
202 case Silver: assign(192, 192, 192);
break;
203 case White: assign(255, 255, 255);
break;
204 case Yellow: assign(255, 255, 0);
break;
205 default: transparent =
true;
break;
209 std::string toString(Layout
const &)
const
211 std::stringstream ss;
215 ss <<
"rgb(" <<
red <<
"," <<
green <<
"," <<
blue <<
")";
224 void assign(
int r,
int g,
int b)
232 class Fill :
public Serializeable
235 Fill(Color::Defaults color) : color(color) { }
236 Fill(Color color = Color::Transparent)
238 std::string toString(Layout
const & layout)
const
240 std::stringstream ss;
241 ss << attribute(
"fill", color.toString(layout));
248 class Stroke :
public Serializeable
251 Stroke(
double width = -1, Color color = Color::Transparent)
252 : width(width), color(color) { }
253 std::string toString(Layout
const & layout)
const
257 return std::string();
259 std::stringstream ss;
260 ss << attribute(
"stroke-width", translateScale(width, layout)) << attribute(
"stroke", color.toString(layout));
268 class Font :
public Serializeable
271 Font(
double size = 12, std::string
const & family =
"Verdana") : size(size), family(family) { }
272 std::string toString(Layout
const & layout)
const
274 std::stringstream ss;
275 ss << attribute(
"font-size", translateScale(size, layout)) << attribute(
"font-family", family);
283 class Shape :
public Serializeable
286 Shape(Fill
const & fill = Fill(), Stroke
const & stroke = Stroke())
287 : fill(fill), stroke(stroke) { }
289 virtual std::string toString(Layout
const & layout)
const = 0;
290 virtual void offset(Point
const & offset) = 0;
295 template <
typename T>
296 std::string vectorToString(std::vector<T> collection, Layout
const & layout)
298 std::string combination_str;
299 for (
unsigned i = 0; i < collection.size(); ++i)
300 combination_str += collection[i].toString(layout);
302 return combination_str;
305 class Circle :
public Shape
308 Circle(Point
const & center,
double diameter, Fill
const & fill,
309 Stroke
const & stroke = Stroke())
310 : Shape(fill, stroke), center(center), radius(diameter / 2) { }
311 std::string toString(Layout
const & layout)
const
313 std::stringstream ss;
314 ss << elemStart(
"circle") << attribute(
"cx", translateX(center.x, layout))
315 << attribute(
"cy", translateY(center.y, layout))
316 << attribute(
"r", translateScale(radius, layout)) << fill.toString(layout)
317 << stroke.toString(layout) << emptyElemEnd();
320 void offset(Point
const & offset)
322 center.x += offset.x;
323 center.y += offset.y;
330 class Elipse :
public Shape
333 Elipse(Point
const & center,
double width,
double height,
334 Fill
const & fill = Fill(), Stroke
const & stroke = Stroke())
335 : Shape(fill, stroke), center(center), radius_width(width / 2),
336 radius_height(height / 2) { }
337 std::string toString(Layout
const & layout)
const
339 std::stringstream ss;
340 ss << elemStart(
"ellipse") << attribute(
"cx", translateX(center.x, layout))
341 << attribute(
"cy", translateY(center.y, layout))
342 << attribute(
"rx", translateScale(radius_width, layout))
343 << attribute(
"ry", translateScale(radius_height, layout))
344 << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
347 void offset(Point
const & offset)
349 center.x += offset.x;
350 center.y += offset.y;
355 double radius_height;
358 class Rectangle :
public Shape
361 Rectangle(Point
const & edge,
double width,
double height,
362 Fill
const & fill = Fill(), Stroke
const & stroke = Stroke())
363 : Shape(fill, stroke), edge(edge), width(width),
365 std::string toString(Layout
const & layout)
const
367 std::stringstream ss;
368 ss << elemStart(
"rect") << attribute(
"x", translateX(edge.x, layout))
369 << attribute(
"y", translateY(edge.y, layout))
370 << attribute(
"width", translateScale(width, layout))
371 << attribute(
"height", translateScale(height, layout))
372 << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
375 void offset(Point
const & offset)
386 class Line :
public Shape
389 Line(Point
const & start_point, Point
const & end_point,
390 Stroke
const & stroke = Stroke())
391 : Shape(Fill(), stroke), start_point(start_point),
392 end_point(end_point) { }
393 std::string toString(Layout
const & layout)
const
395 std::stringstream ss;
396 ss << elemStart(
"line") << attribute(
"x1", translateX(start_point.x, layout))
397 << attribute(
"y1", translateY(start_point.y, layout))
398 << attribute(
"x2", translateX(end_point.x, layout))
399 << attribute(
"y2", translateY(end_point.y, layout))
400 << stroke.toString(layout) << emptyElemEnd();
403 void offset(Point
const & offset)
405 start_point.x += offset.x;
406 start_point.y += offset.y;
408 end_point.x += offset.x;
409 end_point.y += offset.y;
416 class Polygon :
public Shape
419 Polygon(Fill
const & fill = Fill(), Stroke
const & stroke = Stroke())
420 : Shape(fill, stroke) { }
421 Polygon(Stroke
const & stroke = Stroke()) : Shape(Color::Transparent, stroke) { }
422 Polygon & operator<<(Point
const & point)
424 points.push_back(point);
427 std::string toString(Layout
const & layout)
const
429 std::stringstream ss;
430 ss << elemStart(
"polygon");
433 for (
unsigned i = 0; i < points.size(); ++i)
434 ss << translateX(points[i].x, layout) <<
"," << translateY(points[i].y, layout) <<
" ";
437 ss << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
440 void offset(Point
const & offset)
442 for (
unsigned i = 0; i < points.size(); ++i) {
443 points[i].x += offset.x;
444 points[i].y += offset.y;
448 std::vector<Point> points;
451 class Polyline :
public Shape
454 Polyline(Fill
const & fill = Fill(), Stroke
const & stroke = Stroke())
455 : Shape(fill, stroke) { }
456 Polyline(Stroke
const & stroke = Stroke()) : Shape(Color::Transparent, stroke) { }
457 Polyline(std::vector<Point>
const & points,
458 Fill
const & fill = Fill(), Stroke
const & stroke = Stroke())
459 : Shape(fill, stroke), points(points) { }
460 Polyline & operator<<(Point
const & point)
462 points.push_back(point);
465 std::string toString(Layout
const & layout)
const
467 std::stringstream ss;
468 ss << elemStart(
"polyline");
471 for (
unsigned i = 0; i < points.size(); ++i)
472 ss << translateX(points[i].x, layout) <<
"," << translateY(points[i].y, layout) <<
" ";
475 ss << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
478 void offset(Point
const & offset)
480 for (
unsigned i = 0; i < points.size(); ++i) {
481 points[i].x += offset.x;
482 points[i].y += offset.y;
485 std::vector<Point> points;
488 class Text :
public Shape
491 Text(Point
const & origin, std::string
const & content, Fill
const & fill = Fill(),
492 Font
const & font = Font(), Stroke
const & stroke = Stroke())
493 : Shape(fill, stroke), origin(origin), content(content), font(font) { }
494 std::string toString(Layout
const & layout)
const
496 std::stringstream ss;
497 ss << elemStart(
"text") << attribute(
"x", translateX(origin.x, layout))
498 << attribute(
"y", translateY(origin.y, layout))
499 << fill.toString(layout) << stroke.toString(layout) << font.toString(layout)
500 <<
">" << content << elemEnd(
"text");
503 void offset(Point
const & offset)
505 origin.x += offset.x;
506 origin.y += offset.y;
515 class LineChart :
public Shape
518 LineChart(Dimensions margin = Dimensions(),
double scale = 1,
519 Stroke
const & axis_stroke = Stroke(.5, Color::Purple))
520 : axis_stroke(axis_stroke), margin(margin), scale(scale) { }
521 LineChart & operator<<(Polyline
const & polyline)
523 if (polyline.points.empty())
526 polylines.push_back(polyline);
529 std::string toString(Layout
const & layout)
const
531 if (polylines.empty())
535 for (
unsigned i = 0; i < polylines.size(); ++i)
536 ret += polylineToString(polylines[i], layout);
538 return ret + axisString(layout);
540 void offset(Point
const & offset)
542 for (
unsigned i = 0; i < polylines.size(); ++i)
543 polylines[i].offset(offset);
549 std::vector<Polyline> polylines;
551 optional<Dimensions> getDimensions()
const
553 if (polylines.empty())
554 return optional<Dimensions>();
556 optional<Point> min = getMinPoint(polylines[0].points);
557 optional<Point> max = getMaxPoint(polylines[0].points);
558 for (
unsigned i = 0; i < polylines.size(); ++i) {
559 if (getMinPoint(polylines[i].points)->x < min->x)
560 min->x = getMinPoint(polylines[i].points)->x;
561 if (getMinPoint(polylines[i].points)->y < min->y)
562 min->y = getMinPoint(polylines[i].points)->y;
563 if (getMaxPoint(polylines[i].points)->x > max->x)
564 max->x = getMaxPoint(polylines[i].points)->x;
565 if (getMaxPoint(polylines[i].points)->y > max->y)
566 max->y = getMaxPoint(polylines[i].points)->y;
569 return optional<Dimensions>(Dimensions(max->x - min->x, max->y - min->y));
571 std::string axisString(Layout
const & layout)
const
573 optional<Dimensions> dimensions = getDimensions();
578 double width = dimensions->width * 1.1;
579 double height = dimensions->height * 1.1;
582 Polyline axis(Color::Transparent, axis_stroke);
583 axis << Point(margin.width, margin.height + height) << Point(margin.width, margin.height)
584 << Point(margin.width + width, margin.height);
586 return axis.toString(layout);
588 std::string polylineToString(Polyline
const & polyline, Layout
const & layout)
const
590 Polyline shifted_polyline = polyline;
591 shifted_polyline.offset(Point(margin.width, margin.height));
593 std::vector<Circle> vertices;
594 for (
unsigned i = 0; i < shifted_polyline.points.size(); ++i)
595 vertices.push_back(Circle(shifted_polyline.points[i], getDimensions()->height / 30.0, Color::Black));
597 return shifted_polyline.toString(layout) + vectorToString(vertices, layout);
604 Document(std::string
const & file_name, Layout layout = Layout())
605 : file_name(file_name), layout(layout) { }
607 Document & operator<<(Shape
const & shape)
609 body_nodes_str += shape.toString(layout);
612 std::string toString()
const
614 std::stringstream ss;
615 ss <<
"<?xml " << attribute(
"version",
"1.0") << attribute(
"standalone",
"no")
616 <<
"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
617 <<
"\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<svg "
618 << attribute(
"width", layout.dimensions.width,
"px")
619 << attribute(
"height", layout.dimensions.height,
"px")
620 << attribute(
"xmlns",
"http://www.w3.org/2000/svg")
621 << attribute(
"version",
"1.1") <<
">\n" << body_nodes_str << elemEnd(
"svg");
626 std::ofstream ofs(file_name.c_str());
635 std::string file_name;
638 std::string body_nodes_str;