Belle II Software development
SVGPrimitivePlotter.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8#include <tracking/trackFindingCDC/display/SVGPrimitivePlotter.h>
9
10#include <framework/logging/Logger.h>
11
12#include <fstream>
13
14using namespace Belle2;
15using namespace TrackFindingCDC;
16
19
22 , m_svgContentStream()
23 , m_nIndentationSpaces(s_defaultNIndentationSpaces)
24 , m_svgAttributes()
25{
26}
27
30 , m_svgContentStream()
31 , m_nIndentationSpaces(s_defaultNIndentationSpaces)
32 , m_svgAttributes(svgAttributes)
33{
34}
35
37 : PrimitivePlotter(plotter)
38 , m_svgContentStream(plotter.m_svgContentStream.str(), std::ostringstream::ate)
39 , m_nIndentationSpaces(plotter.m_nIndentationSpaces)
40 , m_svgAttributes(plotter.m_svgAttributes)
41{
42}
43
44std::unique_ptr<PrimitivePlotter> SVGPrimitivePlotter::clone() const
45{
46 return std::make_unique<SVGPrimitivePlotter>(*this);
47}
48
50 float startY,
51 float endX,
52 float endY,
53 const AttributeMap& attributeMap)
54{
55 PrimitivePlotter::drawLine(startX, startY, endX, endY, attributeMap);
56
57 AttributeMap geometryAttributeMap {
58 {"x1", std::to_string(startX)},
59 {"x2", std::to_string(endX)},
60 {"y1", std::to_string(startY)},
61 {"y2", std::to_string(endY)}
62 };
63
64 writeStandAloneTag(m_svgContentStream, "line", geometryAttributeMap, attributeMap);
65
66}
67
68
70 float startY,
71 float endX,
72 float endY,
73 const AttributeMap& attributeMap)
74{
75 PrimitivePlotter::drawArrow(startX, startY, endX, endY, attributeMap);
76
77 AttributeMap geometryAttributeMap {
78 {"x1", std::to_string(startX)},
79 {"x2", std::to_string(endX)},
80 {"y1", std::to_string(startY)},
81 {"y2", std::to_string(endY)},
82 {"marker-end", "url(#endArrow)"}
83 };
84
85 writeStandAloneTag(m_svgContentStream, "line", geometryAttributeMap, attributeMap);
86}
87
88
90 float centerY,
91 float radius,
92 const AttributeMap& attributeMap)
93{
94 PrimitivePlotter::drawCircle(centerX, centerY, radius, attributeMap);
95
96 AttributeMap geometryAttributeMap {
97 {"cx", std::to_string(centerX)},
98 {"cy", std::to_string(centerY)},
99 {"r", std::to_string(std::fabs(radius))}
100 };
101
102 writeStandAloneTag(m_svgContentStream, "circle", geometryAttributeMap, attributeMap);
103
104}
105
106
108 float startY,
109 float endX,
110 float endY,
111 float radius,
112 bool longArc,
113 bool sweepFlag,
114 const AttributeMap& attributeMap)
115{
117 startY,
118 endX,
119 endY,
120 radius,
121 longArc,
122 sweepFlag,
123 attributeMap);
124
125 // Compile the path for a ellipses arc
126 // spelling out all the parts in case somebody want adopt
127 // although only the simpler circle arc case is needed.
128
129 const float radiusX = std::fabs(radius);
130 const float radiusY = std::fabs(radius);
131 const float rotationAngle = 0;
132
133 std::ostringstream pathStream;
134
135 pathStream << "M" << ' ';
136 pathStream << std::to_string(startX) << ' ';
137 pathStream << std::to_string(startY) << ' ';
138 pathStream << "A" << ' ';
139 pathStream << std::to_string(radiusX) << ' ';
140 pathStream << std::to_string(radiusY) << ' ';
141 pathStream << std::to_string(rotationAngle) << ' ';
142 pathStream << std::to_string(longArc) << ' ';
143 pathStream << std::to_string(sweepFlag) << ' ';
144 pathStream << std::to_string(endX) << ' ';
145 pathStream << std::to_string(endY);
146
147 AttributeMap geometryAttributeMap{{"d", pathStream.str()}};
148
149 writeStandAloneTag(m_svgContentStream, "path", geometryAttributeMap, attributeMap);
150}
151
152void SVGPrimitivePlotter::drawCurve(const std::vector<std::array<float, 2>>& points,
153 const std::vector<std::array<float, 2>>& tangents,
154 const AttributeMap& attributeMap)
155{
156 // Magic number for circle approximation with splines
157 static const double k = 4.0 / 3.0 * (std::sqrt(2.0) - 1.0);
158
159 B2ASSERT("Expect number of points and tangents to be the same", points.size() == tangents.size());
160 if (points.size() < 2) return;
161
162 PrimitivePlotter::drawCurve(points, tangents, attributeMap);
163
164 std::ostringstream pathStream;
165
166 // Move to point
167 float startX = std::get<0>(points[0]);
168 float startY = std::get<1>(points[0]);
169
170 pathStream << "M";
171 pathStream << ' ' << std::to_string(startX);
172 pathStream << ' ' << std::to_string(startY);
173
174 for (size_t iCurrent = 0, iNext = 1; iNext < points.size(); iCurrent = iNext, ++iNext) {
175
176 float currentTX = std::get<0>(tangents[iCurrent]);
177 float currentTY = std::get<1>(tangents[iCurrent]);
178
179 float nextTX = std::get<0>(tangents[iNext]);
180 float nextTY = std::get<1>(tangents[iNext]);
181
182 float currentT = std::hypot(currentTX, currentTY);
183 float nextT = std::hypot(nextTX, nextTY);
184
185 currentTX /= currentT;
186 currentTY /= currentT;
187 nextTX /= nextT;
188 nextTY /= nextT;
189
190 float currentX = std::get<0>(points[iCurrent]);
191 float currentY = std::get<1>(points[iCurrent]);
192
193 float nextX = std::get<0>(points[iNext]);
194 float nextY = std::get<1>(points[iNext]);
195
196 // Circle arc angle
197 float alpha = std::atan2(currentTX * nextTY - currentTY * nextTX,
198 currentTX * nextTX + currentTY * nextTY);
199
200 float distance = std::hypot(currentX - nextX, currentY - nextY);
201 float controlLength = k * distance / 2 / std::cos(alpha / 2);
202
203 float currentControlX = currentX + currentTX * controlLength;
204 float currentControlY = currentY + currentTY * controlLength;
205
206 float nextControlX = nextX - nextTX * controlLength;
207 float nextControlY = nextY - nextTY * controlLength;
208
209 pathStream << ' ' << "C";
210
211 pathStream << ' ' << std::to_string(currentControlX);
212 pathStream << ' ' << std::to_string(currentControlY);
213
214 pathStream << ' ' << std::to_string(nextControlX);
215 pathStream << ' ' << std::to_string(nextControlY);
216
217 pathStream << ' ' << std::to_string(nextX);
218 pathStream << ' ' << std::to_string(nextY);
219 }
220
221 AttributeMap geometryAttributeMap{{"d", pathStream.str()}};
222
223 writeStandAloneTag(m_svgContentStream, "path", geometryAttributeMap, attributeMap);
224}
225
227{
228 writeOpeningTag(m_svgContentStream, "g", attributeMap);
229}
230
232{
234}
235
236const std::string SVGPrimitivePlotter::save(const std::string& fileName)
237{
238 // Check indentation
240 B2WARNING("Mismatching calls to startGroup and endGroup detected. "
241 << "Proceeding to write the illforamed result.");
242 }
243
244 int savedNIndentationSpaces = m_nIndentationSpaces;
246
247 std::ofstream outputFileStream;
248 outputFileStream.open(fileName);
249
250 writeSVGHeader(outputFileStream);
251
252 AttributeMap standardAttributeMap {
253 {"baseProfile", "full"},
254 {"ev", "http://www.w3.org/2001/xml-events"},
255 {"version", "1.1"},
256 {"xlink", "http://www.w3.org/1999/xlink"},
257 {"xmlns", "http://www.w3.org/2000/svg"}
258 };
259
260
261 // Combine the viewbox specification from the bounding box
262 // Format "{left} {bottom} {width} {height}"
263 std::ostringstream viewBoxStringStream;
264
265 viewBoxStringStream << getBoundingBox().getLeft();
266 viewBoxStringStream << " ";
267 viewBoxStringStream << getBoundingBox().getBottom();
268 viewBoxStringStream << " ";
269 viewBoxStringStream << getBoundingBox().getWidth();
270 viewBoxStringStream << " ";
271 viewBoxStringStream << getBoundingBox().getHeight();
272
273 AttributeMap variableAttributeMap{
274 {"height", std::to_string(getCanvasHeight())},
275 {"width", std::to_string(getCanvasWidth())},
276 {"viewBox", viewBoxStringStream.str()},
277 };
278
279 variableAttributeMap.insert(m_svgAttributes.begin(), m_svgAttributes.end());
280
281 writeOpeningTag(outputFileStream, "svg", standardAttributeMap, variableAttributeMap);
282
283 writeSVGDefs(outputFileStream);
284
285 // Copy the stream the output and rewind it to its original position.
286 outputFileStream << m_svgContentStream.str();
287
288 writeClosingTag(outputFileStream, "svg");
289
290 outputFileStream.close();
291 m_nIndentationSpaces = savedNIndentationSpaces;
292
293 return fileName;
294}
295
297{
299 m_svgContentStream.str("");
301}
302
304{
306}
307
309{
311}
312
313void SVGPrimitivePlotter::writeSVGHeader(std::ostream& outputStream)
314{
315 outputStream << "<?xml version=\"1.0\" ?>" << std::endl;
316 outputStream << "<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>" << std::endl;
317}
318
319void SVGPrimitivePlotter::writeSVGDefs(std::ostream& outputStream)
320{
321 writeOpeningTag(outputStream, "defs");
322
323 AttributeMap markerAttributes {
324 { "id", "endArrow"},
325 { "viewBox", "0 0 10 10"},
326 { "refX", "1"},
327 { "refY", "5"},
328 { "markerUnits", "strokeWidth"},
329 { "markerWidth", "4"},
330 { "markerHeight", "3"},
331 {"orient", "auto"}
332 };
333
334 writeOpeningTag(outputStream, "marker", markerAttributes);
335
336
337 AttributeMap polylineAttributes {
338 {"points", "0,0 10,5 0,10 1,5"},
339 {"fill", "black"}
340 };
341
342 writeStandAloneTag(outputStream, "polyline", polylineAttributes);
343
344 writeClosingTag(outputStream, "marker");
345 writeClosingTag(outputStream, "defs");
346}
347
348void SVGPrimitivePlotter::writeOpeningTag(std::ostream& outputStream,
349 const std::string& tagName,
350 const AttributeMap& geometryAttributeMap,
351 const AttributeMap& styleAttributeMap)
352{
353 // Indentation
354 outputStream << std::string(m_nIndentationSpaces, ' ');
355
356 // Opening bracket
357 outputStream << '<';
358
359 // Write contained part
360 writeTagIntern(outputStream, tagName, geometryAttributeMap, styleAttributeMap);
361
362 // Closing bracket
363 outputStream << '>';
364
365 // New line
366 outputStream << std::endl;
367
368 indent();
369
370 if (geometryAttributeMap.count("_showAt") + styleAttributeMap.count("_showAt")) {
371 const std::string showAt = geometryAttributeMap.count("_showAt")
372 ? geometryAttributeMap.at("_showAt")
373 : styleAttributeMap.at("_showAt");
374
375 AttributeMap setAttributeMap{
376 {"attributeName", "visibility"},
377 {"to", "hidden"},
378 {"begin", "0s"},
379 {"end", showAt}
380 };
381
382 writeStandAloneTag(outputStream, "set", setAttributeMap);
383 }
384}
385
386void SVGPrimitivePlotter::writeStandAloneTag(std::ostream& outputStream,
387 const std::string& tagName,
388 const AttributeMap& geometryAttributeMap,
389 const AttributeMap& styleAttributeMap)
390{
391 // Special handling for _showAt attribute, which introduces a contained <set> tag
392 if (geometryAttributeMap.count("_showAt") + styleAttributeMap.count("_showAt")) {
393 writeOpeningTag(outputStream, tagName, geometryAttributeMap, styleAttributeMap);
394 writeClosingTag(outputStream, tagName);
395 } else {
396
397 // Indentation
398 outputStream << std::string(m_nIndentationSpaces, ' ');
399
400 // Opening bracket
401 outputStream << '<';
402
403 // Write contained part
404 writeTagIntern(outputStream, tagName, geometryAttributeMap, styleAttributeMap);
405
406 // Closing bracket
407 outputStream << '/' << '>';
408
409 // New line
410 outputStream << std::endl;
411 }
412}
413
414void SVGPrimitivePlotter::writeTagIntern(std::ostream& outputStream,
415 const std::string& tagName,
416 const AttributeMap& geometryAttributeMap,
417 const AttributeMap& styleAttributeMap)
418{
419 // Tag name
420 outputStream << tagName;
421
422 // First attribute map
423 if (not geometryAttributeMap.empty()) {
424 outputStream << ' ';
425 writeAttributes(outputStream, geometryAttributeMap);
426 }
427
428 // Second attribute map
429 if (not styleAttributeMap.empty()) {
430 outputStream << ' ';
431 writeAttributes(outputStream, styleAttributeMap);
432 }
433}
434
435void SVGPrimitivePlotter::writeAttributes(std::ostream& outputStream,
436 const AttributeMap& attributeMap)
437{
438 bool first = true;
439 for (const std::pair<std::string, std::string> attribute : attributeMap) {
440
441 const std::string& key = attribute.first;
442 const std::string& value = attribute.second;
443
444 // Skip special attribute
445 if ('_' == key[0]) {
446 continue;
447 }
448
449 // Introduce a space between every attribute
450 if (first) {
451 first = false;
452 } else {
453 outputStream << ' ';
454 }
455
456 outputStream << key;
457 outputStream << '=' << '"';
458 outputStream << value;
459 outputStream << '"';
460 }
461}
462
463void SVGPrimitivePlotter::writeClosingTag(std::ostream& outputStream, const std::string& tagName)
464{
465 dedent();
466
467 // Indentation
468 outputStream << std::string(m_nIndentationSpaces, ' ');
469
470 // Opening bracket
471 outputStream << '<' << '/';
472
473 // Tag name
474 outputStream << tagName;
475
476 // Closing bracket
477 outputStream << '>';
478
479 // New line
480 outputStream << std::endl;
481}
float getBottom() const
Getter for the location of the bottom of the bounding box rectangle (lower y bound)....
Definition: BoundingBox.h:75
float getWidth() const
Getter for the width of the bounding box rectangle.
Definition: BoundingBox.h:63
float getLeft() const
Getter for the location of the left of the bounding box rectangle (lower x bound)....
Definition: BoundingBox.h:71
float getHeight() const
Getter for the height of the bounding box rectangle.
Definition: BoundingBox.h:67
A base class for plots of primitive objects.
float getCanvasWidth()
Getter for the canvas width in pixels.
virtual void drawCurve(const std::vector< std::array< float, 2 > > &points, const std::vector< std::array< float, 2 > > &tangents, const AttributeMap &attributeMap=AttributeMap())
Adds a smooth curve to the plot.
const BoundingBox & getBoundingBox() const
Getter for the bounding box of all drawn objects.
virtual void drawCircleArc(float startX, float startY, float endX, float endY, float radius, bool longArc, bool sweepFlag, const AttributeMap &attributeMap=AttributeMap())
Adds a circle arc to the plot.
virtual void drawArrow(float startX, float startY, float endX, float endY, const AttributeMap &attributeMap=AttributeMap())
Adds an arrow to the plot.
Belle2::TrackFindingCDC::AttributeMap AttributeMap
A map type for attributes names to values for additional drawing information.
float getCanvasHeight()
Getter for the canvas height in pixels.
virtual void clear()
Clears all drawn elements from the plotter.
virtual void drawLine(float startX, float startY, float endX, float endY, const AttributeMap &attributeMap=AttributeMap())
Adds a line to the plot.
virtual void drawCircle(float centerX, float centerY, float radius, const AttributeMap &attributeMap=AttributeMap())
Adds a circle to the plot.
A concrete plotter that can draw primitive objects to standalone SVG files.
void startGroup(const AttributeMap &attributeMap=AttributeMap()) override
Indicates the start of a group of drawn elements.
std::unique_ptr< PrimitivePlotter > clone() const override
Returns a newly created plotter instance containing all information of this.
void indent()
Increases the current indentation by one.
void writeAttributes(std::ostream &outputStream, const AttributeMap &attributeMap)
Writes attribute specification that belongs between the <, > brackets after the tag name.
void drawCurve(const std::vector< std::array< float, 2 > > &points, const std::vector< std::array< float, 2 > > &tangents, const AttributeMap &attributeMap=AttributeMap()) override
Adds a smooth curve to the plot.
void writeStandAloneTag(std::ostream &outputStream, const std::string &tagName, const AttributeMap &geometryAttributeMap=AttributeMap(), const AttributeMap &styleAttributeMap=AttributeMap())
Writes a stand alone xml tag to the given output stream taking attributes from two sources.
std::ostringstream m_svgContentStream
Memory for the plotted elements. This contains only the fragment that is in-between the svg tags and ...
int m_nIndentationSpaces
Memory for the number of spaces that shall be prepended to each line.
void writeSVGDefs(std::ostream &outputStream)
Writes a preamble of definitions that define an arrow cap which can be referenced by lines.
static const int s_defaultNIndentationSpaces
Constant for the number of indentation space to be used within the svg block.
AttributeMap m_svgAttributes
Memory for additional attributes to the top-level svg element.
void drawCircleArc(float startX, float startY, float endX, float endY, float radius, bool longArc, bool sweepFlag, const AttributeMap &attributeMap=AttributeMap()) override
Adds a circle arc to the plot.
void drawCircle(float centerX, float centerY, float radius, const AttributeMap &attributeMap=AttributeMap()) override
Adds a circle to the plot.
SVGPrimitivePlotter()
Default constructor for ROOT compatibility.
void writeSVGHeader(std::ostream &outputStream)
Writes the xml header that indicates that this document will be a SVG document to the given output st...
void drawLine(float startX, float startY, float endX, float endY, const AttributeMap &attributeMap=AttributeMap()) override
Adds a line to the plot.
void drawArrow(float startX, float startY, float endX, float endY, const AttributeMap &attributeMap=AttributeMap()) override
Adds an arrow to the plot.
void writeOpeningTag(std::ostream &outputStream, const std::string &tagName, const AttributeMap &geometryAttributeMap=AttributeMap(), const AttributeMap &styleAttributeMap=AttributeMap())
Writes an opening xml tag to the given output stream taking attributes from two sources.
void dedent()
Decreases the current indentation by one.
void clear() override
Clears all drawn elements from the plotter.
static const int s_addtionalNIndentationSpaces
Constant for the additional number of space to be prepended with each open tag group.
const std::string save(const std::string &fileName) override
Saves the current plot state to a file.
void writeTagIntern(std::ostream &outputStream, const std::string &tagName, const AttributeMap &geometryAttributeMap=AttributeMap(), const AttributeMap &styleAttributeMap=AttributeMap())
Writes part that belongs between the <, > brackets.
void endGroup() override
Indicates the end of a group of drawn elements.
void writeClosingTag(std::ostream &outputStream, const std::string &tagName)
Writes a closing xml tag to the given output stream.
Abstract base class for different kinds of events.
STL namespace.