summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorseancarroll <seanc28@gmail.com>2021-01-12 23:29:39 -0600
committerseancarroll <seanc28@gmail.com>2021-01-12 23:29:39 -0600
commit859d57e497c726ef39f283e49387d43aa474d7ac (patch)
treeebf3d9c267e698b8c4805864c8c78e9dc8b98c21
parent499713a1d63564be320030dc98b3879b66390bb8 (diff)
downloaddotavious-859d57e497c726ef39f283e49387d43aa474d7ac.zip
support subgraphs
-rw-r--r--src/attributes/mod.rs832
-rw-r--r--src/attributes/shape.rs6
-rw-r--r--src/dot.rs1126
-rw-r--r--src/lib.rs13
-rw-r--r--tests/dot.rs106
5 files changed, 1218 insertions, 865 deletions
diff --git a/src/attributes/mod.rs b/src/attributes/mod.rs
index ea4427a..450440e 100644
--- a/src/attributes/mod.rs
+++ b/src/attributes/mod.rs
@@ -330,17 +330,12 @@ pub trait AttributeStatement<'a> {
if self.get_attributes().is_empty() {
return String::from("");
}
- let mut dot_string = format!("{} [", self.get_attribute_statement_type());
- let attributes = &self.get_attributes();
- let mut iter = attributes.iter();
- let first = iter.next().unwrap();
- dot_string.push_str(format!("{}={}", first.0, first.1.dot_string()).as_str());
- for (key, value) in iter {
- dot_string.push_str(", ");
- dot_string.push_str(format!("{}={}", key, value.dot_string()).as_str());
- }
- dot_string.push_str("];");
- dot_string.to_string()
+
+ format!(
+ "{}{};",
+ self.get_attribute_statement_type(),
+ fmt_attributes(self.get_attributes())
+ )
}
}
@@ -399,6 +394,14 @@ pub trait GraphAttributes<'a> {
self.add_attribute("clusterrank", AttributeText::from(cluster_rank))
}
+ /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
+ /// If any fraction is used, the colors are drawn in series, with each color being given
+ /// roughly its specified fraction of the edge.
+ fn color(&mut self, color: Color<'a>) -> &mut Self {
+ Attributes::color(self.get_attributes_mut(), color);
+ self
+ }
+
/// This attribute specifies a color scheme namespace: the context for interpreting color names.
/// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
/// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
@@ -1159,6 +1162,813 @@ impl Attributes {
}
}
+pub trait NodeAttributes<'a> {
+ // TODO: constrain
+ /// Indicates the preferred area for a node or empty cluster when laid out by patchwork.
+ /// default: 1.0, minimum: >0
+ fn area(&mut self, area: f32) -> &mut Self {
+ self.add_attribute("area", AttributeText::from(area))
+ }
+
+ /// Classnames to attach to the node’s SVG element.
+ /// Combine with stylesheet for styling SVG output using CSS classnames.
+ /// Multiple space-separated classes are supported.
+ fn class(&mut self, class: String) -> &mut Self {
+ Attributes::class(self.get_attributes_mut(), class);
+ self
+ }
+
+ /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
+ fn color(&mut self, color: Color<'a>) -> &mut Self {
+ Attributes::color(self.get_attributes_mut(), color);
+ self
+ }
+
+ /// This attribute specifies a color scheme namespace: the context for interpreting color names.
+ /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
+ /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
+ /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
+ fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
+ Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
+ self
+ }
+
+ /// Comments are inserted into output. Device-dependent
+ fn comment(&mut self, comment: String) -> &mut Self {
+ Attributes::comment(self.get_attributes_mut(), comment);
+ self
+ }
+
+ /// Distortion factor for shape=polygon.
+ /// Positive values cause top part to be larger than bottom; negative values do the opposite.
+ fn distortion(&mut self, distortion: f32) -> &mut Self {
+ self.add_attribute("distortion", AttributeText::from(distortion))
+ }
+
+ /// Color used to fill the background of a node or cluster assuming style=filled, or a filled arrowhead.
+ fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
+ Attributes::fill_color(self.get_attributes_mut(), fill_color);
+ self
+ }
+
+ /// Color used to fill the background, with a gradient, of a node or cluster assuming
+ /// style=filled, or a filled arrowhead.
+ fn fill_color_with_colorlist(&mut self, fill_colors: ColorList<'a>) -> &mut Self {
+ Attributes::fill_color_with_colorlist(self.get_attributes_mut(), fill_colors);
+ self
+ }
+
+ /// Color used to fill the background, with a gradient, of a node or cluster assuming
+ /// style=filled, or a filled arrowhead.
+ /// TODO: example
+ fn fill_color_with_iter<I>(&mut self, fill_colors: I) -> &mut Self
+ where
+ I: IntoIterator,
+ I::Item: IntoWeightedColor<'a>,
+ {
+ Attributes::fill_color_with_iter(self.get_attributes_mut(), fill_colors);
+ self
+ }
+
+ /// If true, the node size is specified by the values of the width and height attributes only and
+ /// is not expanded to contain the text label.
+ /// There will be a warning if the label (with margin) cannot fit within these limits.
+ /// If false, the size of a node is determined by smallest width and height needed to contain its label
+ /// and image, if any, with a margin specified by the margin attribute.
+ fn fixed_size(&mut self, fixed_size: bool) -> &mut Self {
+ self.add_attribute("fixedsize", AttributeText::from(fixed_size))
+ }
+
+ /// Color used for text.
+ fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
+ Attributes::font_color(self.get_attributes_mut(), font_color);
+ self
+ }
+
+ /// Font used for text.
+ fn font_name(&mut self, font_name: String) -> &mut Self {
+ Attributes::font_name(self.get_attributes_mut(), font_name);
+ self
+ }
+
+ /// Font size, in points, used for text.
+ /// default: 14.0, minimum: 1.0
+ fn font_size(&mut self, font_size: f32) -> &mut Self {
+ Attributes::font_size(self.get_attributes_mut(), font_size);
+ self
+ }
+
+ /// If a gradient fill is being used, this determines the angle of the fill.
+ fn gradient_angle(&mut self, gradient_angle: u32) -> &mut Self {
+ Attributes::gradient_angle(self.get_attributes_mut(), gradient_angle);
+ self
+ }
+
+ /// If the end points of an edge belong to the same group, i.e., have the same group attribute,
+ /// parameters are set to avoid crossings and keep the edges straight.
+ fn group(&mut self, group: String) -> &mut Self {
+ self.add_attribute("group", AttributeText::attr(group))
+ }
+
+ // TODO: constrain
+ /// Height of node, in inches.
+ /// default: 0.5, minimum: 0.02
+ fn height(&mut self, height: f32) -> &mut Self {
+ self.add_attribute("height", AttributeText::from(height))
+ }
+
+ /// Gives the name of a file containing an image to be displayed inside a node.
+ /// The image file must be in one of the recognized formats,
+ /// typically JPEG, PNG, GIF, BMP, SVG, or Postscript, and be able to be converted
+ /// into the desired output format.
+ fn image(&mut self, image: String) -> &mut Self {
+ self.add_attribute("image", AttributeText::quoted(image))
+ }
+
+ /// Controls how an image is positioned within its containing node.
+ /// Only has an effect when the image is smaller than the containing node.
+ fn image_pos(&mut self, image_pos: ImagePosition) -> &mut Self {
+ self.add_attribute("imagepos", AttributeText::from(image_pos))
+ }
+
+ /// Controls how an image fills its containing node.
+ fn image_scale_bool(&mut self, image_scale: bool) -> &mut Self {
+ self.add_attribute("imagescale", AttributeText::from(image_scale))
+ }
+
+ /// Controls how an image fills its containing node.
+ fn image_scale(&mut self, image_scale: ImageScale) -> &mut Self {
+ self.add_attribute("imagescale", AttributeText::from(image_scale))
+ }
+
+ /// Text label attached to objects.
+ fn label<S: Into<Cow<'a, str>>>(&mut self, text: S) -> &mut Self {
+ self.add_attribute("label", AttributeText::quoted(text))
+ }
+
+ // Vertical placement of labels for nodes, root graphs and clusters.
+ // For graphs and clusters, only labelloc=t and labelloc=b are allowed, corresponding to placement at the top and bottom, respectively.
+ // By default, root graph labels go on the bottom and cluster labels go on the top.
+ // Note that a subgraph inherits attributes from its parent. Thus, if the root graph sets labelloc=b, the subgraph inherits this value.
+ // For nodes, this attribute is used only when the height of the node is larger than the height of its label.
+ // If labelloc=t, labelloc=c, labelloc=b, the label is aligned with the top, centered, or aligned with the bottom of the node, respectively.
+ // By default, the label is vertically centered.
+ fn label_location(&mut self, label_location: LabelLocation) -> &mut Self {
+ Attributes::label_location(self.get_attributes_mut(), label_location);
+ self
+ }
+
+ /// Specifies layers in which the node, edge or cluster is present.
+ fn layer(&mut self, layer: String) -> &mut Self {
+ Attributes::layer(self.get_attributes_mut(), layer);
+ self
+ }
+
+ /// Sets x and y margins of canvas, in inches.
+ /// Both margins are set equal to the given value.
+ /// See [`crate::NodeAttributes::margin_point`]
+ fn margin(&mut self, margin: f32) -> &mut Self {
+ self.margin_point(Point::new_2d(margin, margin))
+ }
+
+ /// Sets x and y margins of canvas, in inches.
+ /// Specifies space left around the node’s label.
+ /// Note that the margin is not part of the drawing but just empty space left around the drawing.
+ /// The margin basically corresponds to a translation of drawing, as would be necessary to
+ /// center a drawing on a page.
+ /// Nothing is actually drawn in the margin.
+ /// To actually extend the background of a drawing, see the pad attribute.
+ /// Whilst it is possible to create a Point value with either a third co-ordinate
+ /// or a forced position, these are ignored for printing.
+ /// By default, the value is 0.11,0.055.
+ fn margin_point(&mut self, margin: Point) -> &mut Self {
+ Attributes::margin(self.get_attributes_mut(), margin);
+ self
+ }
+
+ /// By default, the justification of multi-line labels is done within the largest context that makes sense.
+ /// Thus, in the label of a polygonal node, a left-justified line will align with the left side of the node (shifted by the prescribed margin).
+ /// In record nodes, left-justified line will line up with the left side of the enclosing column of fields.
+ /// If nojustify=true, multi-line labels will be justified in the context of itself.
+ /// For example, if nojustify is set, the first label line is long, and the second is shorter and left-justified,
+ /// the second will align with the left-most character in the first line, regardless of how large the node might be.
+ fn no_justify(&mut self, no_justify: bool) -> &mut Self {
+ Attributes::no_justify(self.get_attributes_mut(), no_justify);
+ self
+ }
+
+ /// If ordering="out", then the outedges of a node, that is, edges with the node as its tail
+ /// node, must appear left-to-right in the same order in which they are defined in the input.
+ ///
+ /// If ordering="in", then the inedges of a node must appear left-to-right in the same order in
+ /// which they are defined in the input.
+ ///
+ /// If defined as a graph or subgraph attribute, the value is applied to all nodes in the graph
+ /// or subgraph.
+ ///
+ /// Note that the graph attribute takes precedence over the node attribute.
+ fn ordering(&mut self, ordering: Ordering) -> &mut Self {
+ Attributes::ordering(self.get_attributes_mut(), ordering);
+ self
+ }
+
+ // TODO: constrain to 0 - 360. Docs say min is 360 which should be max right?
+ /// Angle, in degrees, to rotate polygon node shapes.
+ /// For any number of polygon sides, 0 degrees rotation results in a flat base.
+ /// Used only if rotate is not defined.
+ /// Default: 0.0 and minimum: 360.0
+ fn orientation(&mut self, orientation: f32) -> &mut Self {
+ Attributes::orientation(self.get_attributes_mut(), orientation);
+ self
+ }
+
+ /// Specifies the width of the pen, in points, used to draw lines and curves,
+ /// including the boundaries of edges and clusters.
+ /// default: 1.0, minimum: 0.0
+ fn pen_width(&mut self, pen_width: f32) -> &mut Self {
+ Attributes::pen_width(self.get_attributes_mut(), pen_width);
+ self
+ }
+
+ /// Set number of peripheries used in polygonal shapes and cluster boundaries.
+ fn peripheries(&mut self, peripheries: u32) -> &mut Self {
+ self.add_attribute("penwidth", AttributeText::from(peripheries))
+ }
+
+ /// Position of node, or spline control points.
+ /// the position indicates the center of the node. On output, the coordinates are in points.
+ fn pos(&mut self, pos: Point) -> &mut Self {
+ Attributes::pos(self.get_attributes_mut(), pos);
+ self
+ }
+
+ // TODO: add post_spline
+
+ /// Rectangles for fields of records, in points.
+ fn rects(&mut self, rect: Rectangle) -> &mut Self {
+ self.add_attribute("rects", AttributeText::from(rect))
+ }
+
+ /// If true, force polygon to be regular, i.e., the vertices of the polygon will
+ /// lie on a circle whose center is the center of the node.
+ fn regular(&mut self, regular: bool) -> &mut Self {
+ self.add_attribute("regular", AttributeText::from(regular))
+ }
+
+ /// Gives the number of points used for a circle/ellipse node.
+ fn sample_points(&mut self, sample_points: u32) -> &mut Self {
+ self.add_attribute("samplepoints", AttributeText::from(sample_points))
+ }
+
+ /// Sets the shape of a node.
+ fn shape(&mut self, shape: Shape) -> &mut Self {
+ self.add_attribute("shape", AttributeText::from(shape))
+ }
+
+ // TODO: constrain
+ /// Print guide boxes in PostScript at the beginning of routesplines if showboxes=1, or at the end if showboxes=2.
+ /// (Debugging, TB mode only!)
+ /// default: 0, minimum: 0
+ fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
+ Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
+ self
+ }
+
+ /// Number of sides when shape=polygon.
+ fn sides(&mut self, sides: u32) -> &mut Self {
+ self.add_attribute("sides", AttributeText::from(sides))
+ }
+
+ // TODO: constrain
+ /// Skew factor for shape=polygon.
+ /// Positive values skew top of polygon to right; negative to left.
+ /// default: 0.0, minimum: -100.0
+ fn skew(&mut self, skew: f32) -> &mut Self {
+ self.add_attribute("skew", AttributeText::from(skew))
+ }
+
+ /// If packmode indicates an array packing, sortv specifies an insertion order
+ /// among the components, with smaller values inserted first.
+ /// default: 0, minimum: 0
+ fn sortv(&mut self, sortv: u32) -> &mut Self {
+ Attributes::sortv(self.get_attributes_mut(), sortv);
+ self
+ }
+
+ /// Set style information for components of the graph.
+ fn style(&mut self, style: NodeStyle) -> &mut Self {
+ Attributes::style(self.get_attributes_mut(), Styles::Node(style));
+ self
+ }
+
+ /// If the object has a URL, this attribute determines which window of the browser is used for the URL.
+ fn target(&mut self, target: String) -> &mut Self {
+ Attributes::target(self.get_attributes_mut(), target);
+ self
+ }
+
+ /// Tooltip annotation attached to the node or edge.
+ /// If unset, Graphviz will use the object’s label if defined.
+ /// Note that if the label is a record specification or an HTML-like label,
+ /// the resulting tooltip may be unhelpful.
+ /// In this case, if tooltips will be generated, the user should set a tooltip attribute explicitly.
+ fn tooltip(&mut self, tooltip: String) -> &mut Self {
+ Attributes::tooltip(self.get_attributes_mut(), tooltip);
+ self
+ }
+
+ /// Hyperlinks incorporated into device-dependent output.
+ fn url(&mut self, url: String) -> &mut Self {
+ Attributes::url(self.get_attributes_mut(), url);
+ self
+ }
+
+ /// Sets the coordinates of the vertices of the node’s polygon, in inches.
+ /// A list of points, separated by spaces.
+ fn vertices(&mut self, vertices: String) -> &mut Self {
+ self.add_attribute("vertices", AttributeText::quoted(vertices))
+ }
+
+ /// Width of node, in inches.
+ /// This is taken as the initial, minimum width of the node.
+ /// If fixedsize is true, this will be the final width of the node.
+ /// Otherwise, if the node label requires more width to fit, the node’s
+ /// width will be increased to contain the label.
+ fn width(&mut self, width: f32) -> &mut Self {
+ self.add_attribute("width", AttributeText::from(width))
+ }
+
+ /// External label for a node or edge.
+ /// The label will be placed outside of the node but near it.
+ /// These labels are added after all nodes and edges have been placed.
+ /// The labels will be placed so that they do not overlap any node or label.
+ /// This means it may not be possible to place all of them.
+ /// To force placing all of them, set forcelabels=true.
+ fn xlabel(&mut self, xlabel: String) -> &mut Self {
+ Attributes::xlabel(self.get_attributes_mut(), xlabel);
+ self
+ }
+
+ /// Position of an exterior label, in points.
+ /// The position indicates the center of the label.
+ fn xlp(&mut self, xlp: Point) -> &mut Self {
+ Attributes::xlp(self.get_attributes_mut(), xlp);
+ self
+ }
+
+ /// Add an attribute to the node.
+ fn add_attribute<S: Into<String>>(
+ &mut self,
+ key: S,
+ value: AttributeText<'a>,
+ ) -> &mut Self;
+
+ /// Add multiple attribures to the node.
+ fn add_attributes(
+ &'a mut self,
+ attributes: HashMap<String, AttributeText<'a>>,
+ ) -> &mut Self;
+
+ fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
+}
+
+pub trait EdgeAttributes<'a> {
+ /// Style of arrowhead on the head node of an edge.
+ /// This will only appear if the dir attribute is forward or both.
+ fn arrow_head(&mut self, arrowhead: ArrowType) -> &mut Self {
+ self.add_attribute("arrowhead", AttributeText::from(arrowhead))
+ }
+
+ // TODO: constrain
+ /// Multiplicative scale factor for arrowheads.
+ /// default: 1.0, minimum: 0.0
+ fn arrow_size(&mut self, arrow_size: f32) -> &mut Self {
+ self.add_attribute("arrowsize", AttributeText::from(arrow_size))
+ }
+
+ /// Style of arrowhead on the tail node of an edge.
+ /// This will only appear if the dir attribute is back or both.
+ fn arrowtail(&mut self, arrowtail: ArrowType) -> &mut Self {
+ self.add_attribute("arrowtail", AttributeText::from(arrowtail))
+ }
+
+ /// Classnames to attach to the edge’s SVG element.
+ /// Combine with stylesheet for styling SVG output using CSS classnames.
+ /// Multiple space-separated classes are supported.
+ fn class(&mut self, class: String) -> &mut Self {
+ Attributes::class(self.get_attributes_mut(), class);
+ self
+ }
+
+ /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
+ /// If any fraction is used, the colors are drawn in series, with each color being given
+ /// roughly its specified fraction of the edge.
+ fn color(&mut self, color: Color<'a>) -> &mut Self {
+ Attributes::color(self.get_attributes_mut(), color);
+ self
+ }
+
+ /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
+ /// if colorList has no fractions, the edge is drawn using parallel splines or lines,
+ /// one for each color in the list, in the order given.
+ /// The head arrow, if any, is drawn using the first color in the list, and the tail
+ /// arrow, if any, the second color. This supports the common case of drawing opposing edges,
+ /// but using parallel splines instead of separately routed multiedges.
+ /// If any fraction is used, the colors are drawn in series, with each color being given
+ /// roughly its specified fraction of the edge.
+ fn color_with_colorlist(&mut self, color: ColorList<'a>) -> &mut Self {
+ Attributes::color_with_colorlist(self.get_attributes_mut(), color);
+ self
+ }
+
+ /// This attribute specifies a color scheme namespace: the context for interpreting color names.
+ /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
+ /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
+ /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
+ fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
+ Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
+ self
+ }
+
+ /// Comments are inserted into output. Device-dependent
+ fn comment(&mut self, comment: String) -> &mut Self {
+ self.add_attribute("comment", AttributeText::attr(comment));
+ self
+ }
+
+ /// If false, the edge is not used in ranking the nodes.
+ fn constraint(&mut self, constraint: bool) -> &mut Self {
+ self.add_attribute("constraint", AttributeText::from(constraint))
+ }
+
+ /// If true, attach edge label to edge by a 2-segment polyline, underlining the label,
+ /// then going to the closest point of spline.
+ fn decorate(&mut self, decorate: bool) -> &mut Self {
+ self.add_attribute("decorate", AttributeText::from(decorate))
+ }
+
+ /// Edge type for drawing arrowheads.
+ /// Indicates which ends of the edge should be decorated with an arrowhead.
+ /// The actual style of the arrowhead can be specified using the arrowhead and arrowtail attributes.
+ fn dir(&mut self, dir: Direction) -> &mut Self {
+ self.add_attribute("dir", AttributeText::from(dir))
+ }
+
+ /// If the edge has a URL or edgeURL attribute, edgetarget determines which window
+ /// of the browser is used for the URL attached to the non-label part of the edge.
+ /// Setting edgetarget=_graphviz will open a new window if it doesn’t already exist,
+ /// or reuse it if it does.
+ fn edge_target(&mut self, edge_target: String) -> &mut Self {
+ self.add_attribute("edgetarget", AttributeText::escaped(edge_target))
+ }
+
+ /// Tooltip annotation attached to the non-label part of an edge.
+ /// Used only if the edge has a URL or edgeURL attribute.
+ fn edge_tooltip(&mut self, edge_tooltip: String) -> &mut Self {
+ self.add_attribute("edgetooltip", AttributeText::escaped(edge_tooltip))
+ }
+
+ /// The link for the non-label parts of an edge.
+ /// edgeURL overrides any URL defined for the edge.
+ /// Also, edgeURL is used near the head or tail node unless overridden by headURL or tailURL, respectively.
+ fn edge_url(&mut self, edge_url: String) -> &mut Self {
+ self.add_attribute("edgeurl", AttributeText::escaped(edge_url))
+ }
+
+ // TODO: color list
+ /// Color used to fill the background of a node or cluster assuming style=filled, or a filled arrowhead.
+ fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
+ Attributes::fill_color(self.get_attributes_mut(), fill_color);
+ self
+ }
+
+ // TODO: color list
+ /// Color used for text.
+ fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
+ Attributes::font_color(self.get_attributes_mut(), font_color);
+ self
+ }
+
+ /// Font used for text.
+ fn font_name(&mut self, font_name: String) -> &mut Self {
+ Attributes::font_name(self.get_attributes_mut(), font_name);
+ self
+ }
+
+ /// Font size, in points, used for text.
+ /// default: 14.0, minimum: 1.0
+ fn font_size(&mut self, font_size: f32) -> &mut Self {
+ Attributes::font_size(self.get_attributes_mut(), font_size);
+ self
+ }
+
+ /// Position of an edge’s head label, in points. The position indicates the center of the label.
+ fn head_lp(&mut self, head_lp: Point) -> &mut Self {
+ self.add_attribute("head_lp", AttributeText::from(head_lp))
+ }
+
+ /// If true, the head of an edge is clipped to the boundary of the head node;
+ /// otherwise, the end of the edge goes to the center of the node, or the center
+ /// of a port, if applicable.
+ fn head_clip(&mut self, head_clip: bool) -> &mut Self {
+ self.add_attribute("headclip", AttributeText::from(head_clip))
+ }
+
+ /// Text label to be placed near head of edge.
+ fn head_label(&mut self, head_label: String) -> &mut Self {
+ self.add_attribute("headlabel", AttributeText::quoted(head_label))
+ }
+
+ /// Indicates where on the head node to attach the head of the edge.
+ /// In the default case, the edge is aimed towards the center of the node,
+ /// and then clipped at the node boundary.
+ fn head_port(&mut self, head_port: PortPosition) -> &mut Self {
+ self.add_attribute("headport", AttributeText::from(head_port))
+ }
+
+ /// If the edge has a headURL, headtarget determines which window of the browser is used for the URL.
+ /// Setting headURL=_graphviz will open a new window if the window doesn’t already exist,
+ /// or reuse the window if it does.
+ /// If undefined, the value of the target is used.
+ fn head_target(&mut self, head_target: String) -> &mut Self {
+ self.add_attribute("headtarget", AttributeText::escaped(head_target))
+ }
+
+ /// Tooltip annotation attached to the head of an edge.
+ /// Used only if the edge has a headURL attribute.
+ fn head_tooltip(&mut self, head_tooltip: String) -> &mut Self {
+ self.add_attribute("headtooltip", AttributeText::escaped(head_tooltip))
+ }
+
+ /// If defined, headURL is output as part of the head label of the edge.
+ /// Also, this value is used near the head node, overriding any URL value.
+ fn head_url(&mut self, head_url: String) -> &mut Self {
+ self.add_attribute("headURL", AttributeText::escaped(head_url))
+ }
+
+ /// An escString or an HTML label.
+ fn label(&mut self, label: String) -> &mut Self {
+ Attributes::label(self.get_attributes_mut(), label);
+ self
+ }
+
+ // TODO: constrain
+ /// Determines, along with labeldistance, where the headlabel / taillabel are
+ /// placed with respect to the head / tail in polar coordinates.
+ /// The origin in the coordinate system is the point where the edge touches the node.
+ /// The ray of 0 degrees goes from the origin back along the edge, parallel to the edge at the origin.
+ /// The angle, in degrees, specifies the rotation from the 0 degree ray,
+ /// with positive angles moving counterclockwise and negative angles moving clockwise.
+ /// default: -25.0, minimum: -180.0
+ fn label_angle(&mut self, label_angle: f32) -> &mut Self {
+ self.add_attribute("labelangle", AttributeText::from(label_angle))
+ }
+
+ /// Multiplicative scaling factor adjusting the distance that the headlabel / taillabel is from
+ /// the head / tail node.
+ /// default: 1.0, minimum: 0.0
+ fn label_distance(&mut self, label_distance: f32) -> &mut Self {
+ self.add_attribute("labeldistance", AttributeText::from(label_distance))
+ }
+
+ /// If true, allows edge labels to be less constrained in position.
+ /// In particular, it may appear on top of other edges.
+ fn label_float(&mut self, label_float: bool) -> &mut Self {
+ self.add_attribute("labelfloat", AttributeText::from(label_float))
+ }
+
+ /// Color used for headlabel and taillabel.
+ fn label_font_color(&mut self, label_font_color: Color<'a>) -> &mut Self {
+ self.add_attribute("labelfontcolor", AttributeText::from(label_font_color))
+ }
+
+ /// Font used for headlabel and taillabel.
+ /// If not set, defaults to edge’s fontname.
+ fn label_font_name(&mut self, label_font_name: String) -> &mut Self {
+ self.add_attribute("labelfontname", AttributeText::attr(label_font_name))
+ }
+
+ // TODO: constrains
+ /// Font size, in points, used for headlabel and taillabel.
+ /// If not set, defaults to edge’s fontsize.
+ /// default: 14.0, minimum: 1.0
+ fn label_font_size(&mut self, label_font_size: f32) -> &mut Self {
+ self.add_attribute("labelfontsize", AttributeText::from(label_font_size))
+ }
+
+ /// If the edge has a URL or labelURL attribute, this attribute determines
+ /// which window of the browser is used for the URL attached to the label.
+ fn label_target(&mut self, label_target: String) -> &mut Self {
+ self.add_attribute("labeltarget", AttributeText::escaped(label_target))
+ }
+
+ /// Tooltip annotation attached to label of an edge.
+ /// Used only if the edge has a URL or labelURL attribute.
+ fn label_tooltip(&mut self, label_tooltip: String) -> &mut Self {
+ self.add_attribute("labeltooltip", AttributeText::escaped(label_tooltip))
+ }
+
+ /// If defined, labelURL is the link used for the label of an edge.
+ /// labelURL overrides any URL defined for the edge.
+ fn label_url(&mut self, label_url: String) -> &mut Self {
+ self.add_attribute("labelurl", AttributeText::escaped(label_url))
+ }
+
+ fn layer(&mut self, layer: String) -> &mut Self {
+ Attributes::layer(self.get_attributes_mut(), layer);
+ self
+ }
+
+ fn lhead(&mut self, lhead: String) -> &mut Self {
+ self.add_attribute("lhead", AttributeText::quoted(lhead))
+ }
+
+ /// Label position
+ /// The position indicates the center of the label.
+ fn label_position(&mut self, lp: Point) -> &mut Self {
+ Attributes::label_position(self.get_attributes_mut(), lp);
+ self
+ }
+
+ /// Logical tail of an edge.
+ /// When compound=true, if ltail is defined and is the name of a cluster
+ /// containing the real tail, the edge is clipped to the boundary of the cluster.
+ fn ltail(&mut self, ltail: String) -> &mut Self {
+ self.add_attribute("ltail", AttributeText::quoted(ltail))
+ }
+
+ /// Minimum edge length (rank difference between head and tail).
+ fn min_len(&mut self, min_len: u32) -> &mut Self {
+ self.add_attribute("minlen", AttributeText::from(min_len))
+ }
+
+ fn no_justify(&mut self, no_justify: bool) -> &mut Self {
+ self.add_attribute("nojustify", AttributeText::from(no_justify))
+ }
+
+ fn pen_width(&mut self, pen_width: f32) -> &mut Self {
+ Attributes::pen_width(self.get_attributes_mut(), pen_width);
+ self
+ }
+
+ /// Position of node, or spline control points.
+ /// the position indicates the center of the node. On output, the coordinates are in points.
+ fn pos(&mut self, pos: Point) -> &mut Self {
+ Attributes::pos(self.get_attributes_mut(), pos);
+ self
+ }
+
+ /// Edges with the same head and the same samehead value are aimed at the same point on the head.
+ fn same_head(&mut self, same_head: String) -> &mut Self {
+ self.add_attribute("samehead", AttributeText::quoted(same_head))
+ }
+
+ /// Edges with the same tail and the same sametail value are aimed at the same point on the tail.
+ fn same_tail(&mut self, same_tail: String) -> &mut Self {
+ self.add_attribute("sametail", AttributeText::quoted(same_tail))
+ }
+
+ // TODO: constrain
+ /// Print guide boxes in PostScript at the beginning of routesplines if showboxes=1, or at the
+ /// end if showboxes=2.
+ /// (Debugging, TB mode only!)
+ /// default: 0, minimum: 0
+ fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
+ Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
+ self
+ }
+
+ /// Set style information for components of the graph.
+ fn style(&mut self, style: EdgeStyle) -> &mut Self {
+ Attributes::style(self.get_attributes_mut(), Styles::Edge(style));
+ self
+ }
+
+ /// Position of an edge’s tail label, in points.
+ /// The position indicates the center of the label.
+ fn tail_lp(&mut self, tail_lp: Point) -> &mut Self {
+ self.add_attribute("tail_lp", AttributeText::from(tail_lp))
+ }
+
+ /// If true, the tail of an edge is clipped to the boundary of the tail node; otherwise,
+ /// the end of the edge goes to the center of the node, or the center of a port, if applicable.
+ fn tail_clip(&mut self, tail_clip: bool) -> &mut Self {
+ self.add_attribute("tailclip", AttributeText::from(tail_clip))
+ }
+
+ /// Text label to be placed near tail of edge.
+ fn tail_label(&mut self, tail_label: String) -> &mut Self {
+ self.add_attribute("taillabel", AttributeText::quoted(tail_label))
+ }
+
+ /// Indicates where on the tail node to attach the tail of the edge.
+ fn tail_port(&mut self, tail_port: PortPosition) -> &mut Self {
+ self.add_attribute("tailport", AttributeText::from(tail_port))
+ }
+
+ /// If the edge has a tailURL, tailtarget determines which window of the browser is used for the URL.
+ fn tail_target(&mut self, tail_target: String) -> &mut Self {
+ self.add_attribute("tailtarget", AttributeText::escaped(tail_target))
+ }
+
+ /// Tooltip annotation attached to the tail of an edge.
+ fn tail_tooltip(&mut self, tail_tooltip: String) -> &mut Self {
+ self.add_attribute("tailtooltip", AttributeText::escaped(tail_tooltip))
+ }
+
+ /// If defined, tailURL is output as part of the tail label of the edge.
+ /// Also, this value is used near the tail node, overriding any URL value.
+ fn tail_url(&mut self, tail_url: String) -> &mut Self {
+ self.add_attribute("tailURL", AttributeText::escaped(tail_url))
+ }
+
+ /// If the object has a URL, this attribute determines which window of the browser is used for the URL.
+ fn target(&mut self, target: String) -> &mut Self {
+ self.add_attribute("target", AttributeText::escaped(target))
+ }
+
+ /// Tooltip annotation attached to the node or edge.
+ /// If unset, Graphviz will use the object’s label if defined.
+ /// Note that if the label is a record specification or an HTML-like label,
+ /// the resulting tooltip may be unhelpful.
+ /// In this case, if tooltips will be generated, the user should set a tooltip attribute explicitly.
+ fn tooltip(&mut self, tooltip: String) -> &mut Self {
+ Attributes::tooltip(self.get_attributes_mut(), tooltip);
+ self
+ }
+
+ /// Hyperlinks incorporated into device-dependent output.
+ fn url(&mut self, url: String) -> &mut Self {
+ Attributes::url(self.get_attributes_mut(), url);
+ self
+ }
+
+ // TODO: contrain
+ /// Weight of edge.
+ /// The heavier the weight, the shorter, straighter and more vertical the edge is.
+ /// default: 1, minimum: 0
+ fn weight(&mut self, weight: u32) -> &mut Self {
+ self.add_attribute("weight", AttributeText::attr(weight.to_string()))
+ }
+
+ /// External label for a node or edge.
+ /// The label will be placed outside of the node but near it.
+ /// These labels are added after all nodes and edges have been placed.
+ /// The labels will be placed so that they do not overlap any node or label.
+ /// This means it may not be possible to place all of them.
+ /// To force placing all of them, set forcelabels=true.
+ fn xlabel(&mut self, xlabel: String) -> &mut Self {
+ Attributes::xlabel(self.get_attributes_mut(), xlabel);
+ self
+ }
+
+ /// Position of an exterior label, in points.
+ /// The position indicates the center of the label.
+ fn xlp(&mut self, xlp: Point) -> &mut Self {
+ Attributes::xlp(self.get_attributes_mut(), xlp);
+ self
+ }
+
+ fn add_attribute<S: Into<String>>(
+ &mut self,
+ key: S,
+ value: AttributeText<'a>,
+ ) -> &mut Self;
+
+ fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
+
+ // fn add_attribute<S: Into<String>>(
+ // &self,
+ // key: S,
+ // value: AttributeText<'a>
+ // ) {
+ // self.get_attributes().insert(key.into(), value);
+ // }
+
+ // fn get_attributes(&self) -> IndexMap<String, AttributeText<'a>>;
+
+ // fn get_attributes_mut(&self) -> &mut IndexMap<String, AttributeText<'a>>;
+
+ // fn to_dot_string(&self) -> String;
+}
+
+pub(crate) fn fmt_attributes(attributes: &IndexMap<String, AttributeText>) -> String {
+ let mut dot_string = String::from("");
+ if !attributes.is_empty() {
+ dot_string.push_str(" [");
+ let mut iter = attributes.iter();
+ let first = iter.next().unwrap();
+ dot_string.push_str(format!("{}={}", first.0, first.1.dot_string()).as_str());
+ for (key, value) in iter {
+ dot_string.push_str(", ");
+ dot_string.push_str(format!("{}={}", key, value.dot_string()).as_str());
+ }
+ dot_string.push_str("]");
+ }
+
+ dot_string
+}
+
#[cfg(test)]
mod test {
use crate::attributes::{
diff --git a/src/attributes/shape.rs b/src/attributes/shape.rs
index be37ccc..3c7469e 100644
--- a/src/attributes/shape.rs
+++ b/src/attributes/shape.rs
@@ -91,9 +91,9 @@ impl<'a> DotString<'a> for Shape {
Shape::Invtriangle => "invtriangle".into(),
Shape::Invtrapezium => "invtrapezium".into(),
Shape::Invhouse => "invhouse".into(),
- Shape::Mdiamond => "mdiamond".into(),
- Shape::Msquare => "msquare".into(),
- Shape::Mcircle => "mcircle".into(),
+ Shape::Mdiamond => "Mdiamond".into(),
+ Shape::Msquare => "Msquare".into(),
+ Shape::Mcircle => "Mcircle".into(),
Shape::Record => "record".into(),
Shape::Rect => "rect".into(),
Shape::Rectangle => "rectangle".into(),
diff --git a/src/dot.rs b/src/dot.rs
index 16ec568..1cbb175 100644
--- a/src/dot.rs
+++ b/src/dot.rs
@@ -1,13 +1,11 @@
// TODO: docs
use crate::attributes::{
- ArrowType, AttributeStatement, AttributeText, AttributeType, Attributes, Color,
- ColorList, Direction, EdgeStyle, GraphAttributeStatement, ImagePosition, ImageScale,
- IntoWeightedColor, LabelLocation, NodeStyle, Ordering, Point, PortPosition,
- Rectangle, Shape, Styles,
+ fmt_attributes, AttributeStatement, AttributeText, AttributeType, EdgeAttributes,
+ GraphAttributeStatement, NodeAttributes, PortPosition,
};
use indexmap::IndexMap;
-use std::borrow::{Cow};
+use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::io;
@@ -19,7 +17,6 @@ pub trait DotString<'a> {
fn dot_string(&self) -> Cow<'a, str>;
}
-// TODO: probably dont need this struct and can move impl methods into lib module
pub struct Dot<'a> {
pub graph: Graph<'a>,
}
@@ -33,11 +30,7 @@ impl<'a> Dot<'a> {
self.internal_render(&self.graph, w)
}
- fn internal_render<W>(
- &self,
- graph: &Graph,
- w: &mut W,
- ) -> io::Result<()>
+ fn internal_render<W>(&self, graph: &Graph, w: &mut W) -> io::Result<()>
where
W: Write,
{
@@ -46,7 +39,7 @@ impl<'a> Dot<'a> {
writeln!(w, "// {}", comment)?;
}
- let edge_op = &graph.edge_op();
+ let edge_op = graph.edge_op();
let strict = if graph.strict { "strict " } else { "" };
write!(w, "{}{}", strict, &graph.graph_type())?;
@@ -68,45 +61,98 @@ impl<'a> Dot<'a> {
write!(w, "{}{}\n", INDENT, edge_attributes.dot_string())?;
}
+ for g in &graph.sub_graphs {
+ self.render_subgraph(w, g, edge_op, 1)?;
+ }
+
for n in &graph.nodes {
- // TODO: handle render options
- // Are render options something we need?
- // we could clone the node or and remove the attributes based on render options
- // or maybe we keep a set of attributes to ignore based on the options
writeln!(w, "{}{}", INDENT, n.dot_string())?;
}
for e in graph.edges.iter() {
- let mut edge_source = e.source.to_owned();
- if let Some(source_port_position) = &e.source_port_position {
- edge_source
- .push_str(format!(":{}", source_port_position.dot_string()).as_str())
- }
+ self.render_edge(w, e, edge_op, 1)?;
+ }
- let mut edge_target = e.target.to_owned();
- if let Some(target_port_position) = &e.target_port_position {
- edge_target
- .push_str(format!(":{}", target_port_position.dot_string()).as_str())
- }
+ writeln!(w, "}}")
+ }
- write!(w, "{}{} {} {}", INDENT, edge_source, edge_op, edge_target)?;
- // TODO: render ops
- if !e.attributes.is_empty() {
- write!(w, " [")?;
-
- let mut iter = e.attributes.iter();
- let first = iter.next().unwrap();
- write!(w, "{}={}", first.0, first.1.dot_string())?;
- for (key, value) in iter {
- write!(w, ", ")?;
- write!(w, "{}={}", key, value.dot_string())?;
- }
- write!(w, "]")?;
- }
- writeln!(w, ";")?;
+ fn render_subgraph<W>(
+ &self,
+ w: &mut W,
+ sub_graph: &SubGraph,
+ edge_op: &str,
+ indentation_level: usize,
+ ) -> io::Result<()>
+ where
+ W: Write,
+ {
+ write!(w, "{}subgraph", get_indentation(indentation_level))?;
+ if let Some(id) = &sub_graph.id {
+ write!(w, " {}", id)?;
}
- writeln!(w, "}}")
+ writeln!(w, " {{")?;
+
+ let indent = get_indentation(indentation_level + 1);
+ if let Some(graph_attributes) = &sub_graph.graph_attributes {
+ write!(w, "{}{}\n", indent, graph_attributes.dot_string())?;
+ }
+
+ if let Some(node_attributes) = &sub_graph.node_attributes {
+ write!(w, "{}{}\n", indent, node_attributes.dot_string())?;
+ }
+
+ if let Some(edge_attributes) = &sub_graph.edge_attributes {
+ write!(w, "{}{}\n", indent, edge_attributes.dot_string())?;
+ }
+
+ for g in &sub_graph.sub_graphs {
+ self.render_subgraph(w, g, edge_op, indentation_level + 1)?;
+ }
+
+ for n in &sub_graph.nodes {
+ writeln!(w, "{}{}", indent, n.dot_string())?;
+ }
+
+ for e in sub_graph.edges.iter() {
+ self.render_edge(w, e, edge_op, indentation_level + 1)?;
+ }
+
+ writeln!(w, "{}}}\n", get_indentation(indentation_level))
+ }
+
+ fn render_edge<W>(
+ &self,
+ w: &mut W,
+ edge: &Edge,
+ edge_op: &str,
+ indentation_level: usize,
+ ) -> io::Result<()>
+ where
+ W: Write,
+ {
+ let mut edge_source = edge.source.to_owned();
+ if let Some(source_port_position) = &edge.source_port_position {
+ edge_source
+ .push_str(format!(":{}", source_port_position.dot_string()).as_str())
+ }
+
+ let mut edge_target = edge.target.to_owned();
+ if let Some(target_port_position) = &edge.target_port_position {
+ edge_target
+ .push_str(format!(":{}", target_port_position.dot_string()).as_str())
+ }
+
+ write!(
+ w,
+ "{}{} {} {}",
+ get_indentation(indentation_level),
+ edge_source,
+ edge_op,
+ edge_target
+ )?;
+ write!(w, "{}", fmt_attributes(&edge.attributes))?;
+ writeln!(w, ";")
}
}
@@ -136,6 +182,7 @@ pub enum RenderOption {
EdgeIndexLabel,
}
+#[derive(Clone, Debug)]
pub struct Graph<'a> {
pub id: Option<String>,
@@ -152,6 +199,8 @@ pub struct Graph<'a> {
pub edge_attributes: Option<EdgeAttributeStatement<'a>>,
+ pub sub_graphs: Vec<SubGraph<'a>>,
+
pub nodes: Vec<Node<'a>>,
pub edges: Vec<Edge<'a>>,
@@ -166,6 +215,7 @@ impl<'a> Graph<'a> {
graph_attributes: Option<GraphAttributeStatement<'a>>,
node_attributes: Option<NodeAttributeStatement<'a>>,
edge_attributes: Option<EdgeAttributeStatement<'a>>,
+ sub_graphs: Vec<SubGraph<'a>>,
nodes: Vec<Node<'a>>,
edges: Vec<Edge<'a>>,
) -> Self {
@@ -177,6 +227,7 @@ impl<'a> Graph<'a> {
graph_attributes,
node_attributes,
edge_attributes,
+ sub_graphs,
nodes,
edges,
}
@@ -212,6 +263,8 @@ pub struct GraphBuilder<'a> {
edge_attributes: Option<EdgeAttributeStatement<'a>>,
+ sub_graphs: Vec<SubGraph<'a>>,
+
nodes: Vec<Node<'a>>,
edges: Vec<Edge<'a>>,
@@ -229,6 +282,7 @@ impl<'a> GraphBuilder<'a> {
graph_attributes: None,
node_attributes: None,
edge_attributes: None,
+ sub_graphs: Vec::new(),
nodes: Vec::new(),
edges: Vec::new(),
comment: None,
@@ -243,6 +297,7 @@ impl<'a> GraphBuilder<'a> {
graph_attributes: None,
node_attributes: None,
edge_attributes: None,
+ sub_graphs: Vec::new(),
nodes: Vec::new(),
edges: Vec::new(),
comment: None,
@@ -335,6 +390,11 @@ impl<'a> GraphBuilder<'a> {
self
}
+ pub fn add_sub_graph(&mut self, sub_graph: SubGraph<'a>) -> &mut Self {
+ self.sub_graphs.push(sub_graph);
+ self
+ }
+
pub fn add_node(&mut self, node: Node<'a>) -> &mut Self {
self.nodes.push(node);
self
@@ -359,6 +419,185 @@ impl<'a> GraphBuilder<'a> {
graph_attributes: self.graph_attributes.clone(),
node_attributes: self.node_attributes.clone(),
edge_attributes: self.edge_attributes.clone(),
+ sub_graphs: self.sub_graphs.clone(),
+ nodes: self.nodes.clone(), // TODO: is clone the only option here?
+ edges: self.edges.clone(), // TODO: is clone the only option here?
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct SubGraph<'a> {
+ pub id: Option<String>,
+
+ pub graph_attributes: Option<GraphAttributeStatement<'a>>,
+
+ pub node_attributes: Option<NodeAttributeStatement<'a>>,
+
+ pub edge_attributes: Option<EdgeAttributeStatement<'a>>,
+
+ pub sub_graphs: Vec<SubGraph<'a>>,
+
+ pub nodes: Vec<Node<'a>>,
+
+ pub edges: Vec<Edge<'a>>,
+}
+
+impl<'a> SubGraph<'a> {
+ pub fn new(
+ id: Option<String>,
+ graph_attributes: Option<GraphAttributeStatement<'a>>,
+ node_attributes: Option<NodeAttributeStatement<'a>>,
+ edge_attributes: Option<EdgeAttributeStatement<'a>>,
+ sub_graphs: Vec<SubGraph<'a>>,
+ nodes: Vec<Node<'a>>,
+ edges: Vec<Edge<'a>>,
+ ) -> Self {
+ Self {
+ id,
+ graph_attributes,
+ node_attributes,
+ edge_attributes,
+ sub_graphs,
+ nodes,
+ edges,
+ }
+ }
+}
+
+pub struct SubGraphBuilder<'a> {
+ id: Option<String>,
+
+ graph_attributes: Option<GraphAttributeStatement<'a>>,
+
+ node_attributes: Option<NodeAttributeStatement<'a>>,
+
+ edge_attributes: Option<EdgeAttributeStatement<'a>>,
+
+ sub_graphs: Vec<SubGraph<'a>>,
+
+ nodes: Vec<Node<'a>>,
+
+ edges: Vec<Edge<'a>>,
+}
+
+// TODO: id should be an escString
+impl<'a> SubGraphBuilder<'a> {
+ pub fn new(id: Option<String>) -> Self {
+ Self {
+ id,
+ graph_attributes: None,
+ node_attributes: None,
+ edge_attributes: None,
+ sub_graphs: Vec::new(),
+ nodes: Vec::new(),
+ edges: Vec::new(),
+ }
+ }
+
+ pub fn add_graph_attributes(
+ &mut self,
+ graph_attributes: GraphAttributeStatement<'a>,
+ ) -> &mut Self {
+ self.graph_attributes = Some(graph_attributes);
+ self
+ }
+
+ pub fn add_node_attributes(
+ &mut self,
+ node_attributes: NodeAttributeStatement<'a>,
+ ) -> &mut Self {
+ self.node_attributes = Some(node_attributes);
+ self
+ }
+
+ pub fn add_edge_attributes(
+ &mut self,
+ edge_attributes: EdgeAttributeStatement<'a>,
+ ) -> &mut Self {
+ self.edge_attributes = Some(edge_attributes);
+ self
+ }
+
+ // TODO: update to insert into appropriate statement or remove?
+ // pub fn add_attribute(
+ // &mut self,
+ // attribute_type: AttributeType,
+ // key: String, value: AttributeText<'a>
+ // ) -> &mut Self {
+ // self.get_attributes(attribute_type).insert(key, value);
+ // self
+ // }
+ //
+ // pub fn add_attributes(
+ // &mut self,
+ // attribute_type: AttributeType,
+ // attributes: HashMap<String, AttributeText<'a>>
+ // ) -> &mut Self {
+ // self.get_attributes(attribute_type).extend(attributes);
+ // self
+ // }
+
+ pub fn add_attribute(
+ &mut self,
+ attribute_type: AttributeType,
+ key: String,
+ value: AttributeText<'a>,
+ ) -> &mut Self {
+ match attribute_type {
+ AttributeType::Graph => {
+ if self.graph_attributes.is_none() {
+ self.graph_attributes = Some(GraphAttributeStatement::new());
+ }
+ self.graph_attributes
+ .as_mut()
+ .unwrap()
+ .add_attribute(key, value);
+ }
+ AttributeType::Edge => {
+ if self.edge_attributes.is_none() {
+ self.edge_attributes = Some(EdgeAttributeStatement::new());
+ }
+ self.edge_attributes
+ .as_mut()
+ .unwrap()
+ .add_attribute(key, value);
+ }
+ AttributeType::Node => {
+ if self.node_attributes.is_none() {
+ self.node_attributes = Some(NodeAttributeStatement::new());
+ }
+ self.node_attributes
+ .as_mut()
+ .unwrap()
+ .add_attribute(key, value);
+ }
+ }
+ self
+ }
+
+ pub fn add_sub_graph(&mut self, sub_graph: SubGraph<'a>) -> &mut Self {
+ self.sub_graphs.push(sub_graph);
+ self
+ }
+
+ pub fn add_node(&mut self, node: Node<'a>) -> &mut Self {
+ self.nodes.push(node);
+ self
+ }
+
+ pub fn add_edge(&mut self, edge: Edge<'a>) -> &mut Self {
+ self.edges.push(edge);
+ self
+ }
+
+ pub fn build(&self) -> SubGraph<'a> {
+ SubGraph {
+ id: self.id.to_owned(),
+ graph_attributes: self.graph_attributes.clone(),
+ node_attributes: self.node_attributes.clone(),
+ edge_attributes: self.edge_attributes.clone(),
+ sub_graphs: self.sub_graphs.clone(),
nodes: self.nodes.clone(), // TODO: is clone the only option here?
edges: self.edges.clone(), // TODO: is clone the only option here?
}
@@ -384,19 +623,7 @@ impl<'a> Node<'a> {
impl<'a> DotString<'a> for Node<'a> {
fn dot_string(&self) -> Cow<'a, str> {
let mut dot_string = format!("{}", &self.id);
- if !self.attributes.is_empty() {
- dot_string.push_str(" [");
- let mut iter = self.attributes.iter();
- let first = iter.next().unwrap();
- dot_string
- .push_str(format!("{}={}", first.0, first.1.dot_string()).as_str());
- for (key, value) in iter {
- dot_string.push_str(", ");
- dot_string.push_str(format!("{}={}", key, value.dot_string()).as_str());
- }
-
- dot_string.push_str("]");
- }
+ dot_string.push_str(fmt_attributes(&self.attributes).as_str());
dot_string.push_str(";");
dot_string.into()
}
@@ -579,381 +806,6 @@ impl<'a> EdgeBuilder<'a> {
}
}
-pub trait NodeAttributes<'a> {
- // TODO: constrain
- /// Indicates the preferred area for a node or empty cluster when laid out by patchwork.
- /// default: 1.0, minimum: >0
- fn area(&mut self, area: f32) -> &mut Self {
- self.add_attribute("area", AttributeText::from(area))
- }
-
- /// Classnames to attach to the node’s SVG element.
- /// Combine with stylesheet for styling SVG output using CSS classnames.
- /// Multiple space-separated classes are supported.
- fn class(&mut self, class: String) -> &mut Self {
- Attributes::class(self.get_attributes_mut(), class);
- self
- }
-
- /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
- fn color(&mut self, color: Color<'a>) -> &mut Self {
- Attributes::color(self.get_attributes_mut(), color);
- self
- }
-
- fn color_with_colorlist(&mut self, color: ColorList<'a>) -> &mut Self {
- Attributes::color_with_colorlist(self.get_attributes_mut(), color);
- self
- }
-
- /// This attribute specifies a color scheme namespace: the context for interpreting color names.
- /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
- /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
- /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
- fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
- Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
- self
- }
-
- /// Comments are inserted into output. Device-dependent
- fn comment(&mut self, comment: String) -> &mut Self {
- Attributes::comment(self.get_attributes_mut(), comment);
- self
- }
-
- /// Distortion factor for shape=polygon.
- /// Positive values cause top part to be larger than bottom; negative values do the opposite.
- fn distortion(&mut self, distortion: f32) -> &mut Self {
- self.add_attribute("distortion", AttributeText::from(distortion))
- }
-
- /// Color used to fill the background of a node or cluster assuming style=filled, or a filled arrowhead.
- fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
- Attributes::fill_color(self.get_attributes_mut(), fill_color);
- self
- }
-
- /// Color used to fill the background, with a gradient, of a node or cluster assuming
- /// style=filled, or a filled arrowhead.
- fn fill_color_with_colorlist(&mut self, fill_colors: ColorList<'a>) -> &mut Self {
- Attributes::fill_color_with_colorlist(self.get_attributes_mut(), fill_colors);
- self
- }
-
- /// Color used to fill the background, with a gradient, of a node or cluster assuming
- /// style=filled, or a filled arrowhead.
- /// TODO: example
- fn fill_color_with_iter<I>(&mut self, fill_colors: I) -> &mut Self
- where
- I: IntoIterator,
- I::Item: IntoWeightedColor<'a>,
- {
- Attributes::fill_color_with_iter(self.get_attributes_mut(), fill_colors);
- self
- }
-
- /// If true, the node size is specified by the values of the width and height attributes only and
- /// is not expanded to contain the text label.
- /// There will be a warning if the label (with margin) cannot fit within these limits.
- /// If false, the size of a node is determined by smallest width and height needed to contain its label
- /// and image, if any, with a margin specified by the margin attribute.
- fn fixed_size(&mut self, fixed_size: bool) -> &mut Self {
- self.add_attribute("fixedsize", AttributeText::from(fixed_size))
- }
-
- /// Color used for text.
- fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
- Attributes::font_color(self.get_attributes_mut(), font_color);
- self
- }
-
- /// Font used for text.
- fn font_name(&mut self, font_name: String) -> &mut Self {
- Attributes::font_name(self.get_attributes_mut(), font_name);
- self
- }
-
- /// Font size, in points, used for text.
- /// default: 14.0, minimum: 1.0
- fn font_size(&mut self, font_size: f32) -> &mut Self {
- Attributes::font_size(self.get_attributes_mut(), font_size);
- self
- }
-
- /// If a gradient fill is being used, this determines the angle of the fill.
- fn gradient_angle(&mut self, gradient_angle: u32) -> &mut Self {
- Attributes::gradient_angle(self.get_attributes_mut(), gradient_angle);
- self
- }
-
- /// If the end points of an edge belong to the same group, i.e., have the same group attribute,
- /// parameters are set to avoid crossings and keep the edges straight.
- fn group(&mut self, group: String) -> &mut Self {
- self.add_attribute("group", AttributeText::attr(group))
- }
-
- // TODO: constrain
- /// Height of node, in inches.
- /// default: 0.5, minimum: 0.02
- fn height(&mut self, height: f32) -> &mut Self {
- self.add_attribute("height", AttributeText::from(height))
- }
-
- /// Gives the name of a file containing an image to be displayed inside a node.
- /// The image file must be in one of the recognized formats,
- /// typically JPEG, PNG, GIF, BMP, SVG, or Postscript, and be able to be converted
- /// into the desired output format.
- fn image(&mut self, image: String) -> &mut Self {
- self.add_attribute("image", AttributeText::quoted(image))
- }
-
- /// Controls how an image is positioned within its containing node.
- /// Only has an effect when the image is smaller than the containing node.
- fn image_pos(&mut self, image_pos: ImagePosition) -> &mut Self {
- self.add_attribute("imagepos", AttributeText::from(image_pos))
- }
-
- /// Controls how an image fills its containing node.
- fn image_scale_bool(&mut self, image_scale: bool) -> &mut Self {
- self.add_attribute("imagescale", AttributeText::from(image_scale))
- }
-
- /// Controls how an image fills its containing node.
- fn image_scale(&mut self, image_scale: ImageScale) -> &mut Self {
- self.add_attribute("imagescale", AttributeText::from(image_scale))
- }
-
- /// Text label attached to objects.
- fn label<S: Into<Cow<'a, str>>>(&mut self, text: S) -> &mut Self {
- self.add_attribute("label", AttributeText::quoted(text))
- }
-
- // Vertical placement of labels for nodes, root graphs and clusters.
- // For graphs and clusters, only labelloc=t and labelloc=b are allowed, corresponding to placement at the top and bottom, respectively.
- // By default, root graph labels go on the bottom and cluster labels go on the top.
- // Note that a subgraph inherits attributes from its parent. Thus, if the root graph sets labelloc=b, the subgraph inherits this value.
- // For nodes, this attribute is used only when the height of the node is larger than the height of its label.
- // If labelloc=t, labelloc=c, labelloc=b, the label is aligned with the top, centered, or aligned with the bottom of the node, respectively.
- // By default, the label is vertically centered.
- fn label_location(&mut self, label_location: LabelLocation) -> &mut Self {
- Attributes::label_location(self.get_attributes_mut(), label_location);
- self
- }
-
- /// Specifies layers in which the node, edge or cluster is present.
- fn layer(&mut self, layer: String) -> &mut Self {
- Attributes::layer(self.get_attributes_mut(), layer);
- self
- }
-
- /// Sets x and y margins of canvas, in inches.
- /// Both margins are set equal to the given value.
- /// See [`crate::NodeAttributes::margin_point`]
- fn margin(&mut self, margin: f32) -> &mut Self {
- self.margin_point(Point::new_2d(margin, margin))
- }
-
- /// Sets x and y margins of canvas, in inches.
- /// Specifies space left around the node’s label.
- /// Note that the margin is not part of the drawing but just empty space left around the drawing.
- /// The margin basically corresponds to a translation of drawing, as would be necessary to
- /// center a drawing on a page.
- /// Nothing is actually drawn in the margin.
- /// To actually extend the background of a drawing, see the pad attribute.
- /// Whilst it is possible to create a Point value with either a third co-ordinate
- /// or a forced position, these are ignored for printing.
- /// By default, the value is 0.11,0.055.
- fn margin_point(&mut self, margin: Point) -> &mut Self {
- Attributes::margin(self.get_attributes_mut(), margin);
- self
- }
-
- /// By default, the justification of multi-line labels is done within the largest context that makes sense.
- /// Thus, in the label of a polygonal node, a left-justified line will align with the left side of the node (shifted by the prescribed margin).
- /// In record nodes, left-justified line will line up with the left side of the enclosing column of fields.
- /// If nojustify=true, multi-line labels will be justified in the context of itself.
- /// For example, if nojustify is set, the first label line is long, and the second is shorter and left-justified,
- /// the second will align with the left-most character in the first line, regardless of how large the node might be.
- fn no_justify(&mut self, no_justify: bool) -> &mut Self {
- Attributes::no_justify(self.get_attributes_mut(), no_justify);
- self
- }
-
- /// If ordering="out", then the outedges of a node, that is, edges with the node as its tail
- /// node, must appear left-to-right in the same order in which they are defined in the input.
- ///
- /// If ordering="in", then the inedges of a node must appear left-to-right in the same order in
- /// which they are defined in the input.
- ///
- /// If defined as a graph or subgraph attribute, the value is applied to all nodes in the graph
- /// or subgraph.
- ///
- /// Note that the graph attribute takes precedence over the node attribute.
- fn ordering(&mut self, ordering: Ordering) -> &mut Self {
- Attributes::ordering(self.get_attributes_mut(), ordering);
- self
- }
-
- // TODO: constrain to 0 - 360. Docs say min is 360 which should be max right?
- /// Angle, in degrees, to rotate polygon node shapes.
- /// For any number of polygon sides, 0 degrees rotation results in a flat base.
- /// Used only if rotate is not defined.
- /// Default: 0.0 and minimum: 360.0
- fn orientation(&mut self, orientation: f32) -> &mut Self {
- Attributes::orientation(self.get_attributes_mut(), orientation);
- self
- }
-
- /// Specifies the width of the pen, in points, used to draw lines and curves,
- /// including the boundaries of edges and clusters.
- /// default: 1.0, minimum: 0.0
- fn pen_width(&mut self, pen_width: f32) -> &mut Self {
- Attributes::pen_width(self.get_attributes_mut(), pen_width);
- self
- }
-
- /// Set number of peripheries used in polygonal shapes and cluster boundaries.
- fn peripheries(&mut self, peripheries: u32) -> &mut Self {
- self.add_attribute("penwidth", AttributeText::from(peripheries))
- }
-
- /// Position of node, or spline control points.
- /// the position indicates the center of the node. On output, the coordinates are in points.
- fn pos(&mut self, pos: Point) -> &mut Self {
- Attributes::pos(self.get_attributes_mut(), pos);
- self
- }
-
- // TODO: add post_spline
-
- /// Rectangles for fields of records, in points.
- fn rects(&mut self, rect: Rectangle) -> &mut Self {
- self.add_attribute("rects", AttributeText::from(rect))
- }
-
- /// If true, force polygon to be regular, i.e., the vertices of the polygon will
- /// lie on a circle whose center is the center of the node.
- fn regular(&mut self, regular: bool) -> &mut Self {
- self.add_attribute("regular", AttributeText::from(regular))
- }
-
- /// Gives the number of points used for a circle/ellipse node.
- fn sample_points(&mut self, sample_points: u32) -> &mut Self {
- self.add_attribute("samplepoints", AttributeText::from(sample_points))
- }
-
- /// Sets the shape of a node.
- fn shape(&mut self, shape: Shape) -> &mut Self {
- self.add_attribute("shape", AttributeText::from(shape))
- }
-
- // TODO: constrain
- /// Print guide boxes in PostScript at the beginning of routesplines if showboxes=1, or at the end if showboxes=2.
- /// (Debugging, TB mode only!)
- /// default: 0, minimum: 0
- fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
- Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
- self
- }
-
- /// Number of sides when shape=polygon.
- fn sides(&mut self, sides: u32) -> &mut Self {
- self.add_attribute("sides", AttributeText::from(sides))
- }
-
- // TODO: constrain
- /// Skew factor for shape=polygon.
- /// Positive values skew top of polygon to right; negative to left.
- /// default: 0.0, minimum: -100.0
- fn skew(&mut self, skew: f32) -> &mut Self {
- self.add_attribute("skew", AttributeText::from(skew))
- }
-
- /// If packmode indicates an array packing, sortv specifies an insertion order
- /// among the components, with smaller values inserted first.
- /// default: 0, minimum: 0
- fn sortv(&mut self, sortv: u32) -> &mut Self {
- Attributes::sortv(self.get_attributes_mut(), sortv);
- self
- }
-
- /// Set style information for components of the graph.
- fn style(&mut self, style: NodeStyle) -> &mut Self {
- Attributes::style(self.get_attributes_mut(), Styles::Node(style));
- self
- }
-
- /// If the object has a URL, this attribute determines which window of the browser is used for the URL.
- fn target(&mut self, target: String) -> &mut Self {
- Attributes::target(self.get_attributes_mut(), target);
- self
- }
-
- /// Tooltip annotation attached to the node or edge.
- /// If unset, Graphviz will use the object’s label if defined.
- /// Note that if the label is a record specification or an HTML-like label,
- /// the resulting tooltip may be unhelpful.
- /// In this case, if tooltips will be generated, the user should set a tooltip attribute explicitly.
- fn tooltip(&mut self, tooltip: String) -> &mut Self {
- Attributes::tooltip(self.get_attributes_mut(), tooltip);
- self
- }
-
- /// Hyperlinks incorporated into device-dependent output.
- fn url(&mut self, url: String) -> &mut Self {
- Attributes::url(self.get_attributes_mut(), url);
- self
- }
-
- /// Sets the coordinates of the vertices of the node’s polygon, in inches.
- /// A list of points, separated by spaces.
- fn vertices(&mut self, vertices: String) -> &mut Self {
- self.add_attribute("vertices", AttributeText::quoted(vertices))
- }
-
- /// Width of node, in inches.
- /// This is taken as the initial, minimum width of the node.
- /// If fixedsize is true, this will be the final width of the node.
- /// Otherwise, if the node label requires more width to fit, the node’s
- /// width will be increased to contain the label.
- fn width(&mut self, width: f32) -> &mut Self {
- self.add_attribute("width", AttributeText::from(width))
- }
-
- /// External label for a node or edge.
- /// The label will be placed outside of the node but near it.
- /// These labels are added after all nodes and edges have been placed.
- /// The labels will be placed so that they do not overlap any node or label.
- /// This means it may not be possible to place all of them.
- /// To force placing all of them, set forcelabels=true.
- fn xlabel(&mut self, xlabel: String) -> &mut Self {
- Attributes::xlabel(self.get_attributes_mut(), xlabel);
- self
- }
-
- /// Position of an exterior label, in points.
- /// The position indicates the center of the label.
- fn xlp(&mut self, xlp: Point) -> &mut Self {
- Attributes::xlp(self.get_attributes_mut(), xlp);
- self
- }
-
- /// Add an attribute to the node.
- fn add_attribute<S: Into<String>>(
- &mut self,
- key: S,
- value: AttributeText<'a>,
- ) -> &mut Self;
-
- /// Add multiple attribures to the node.
- fn add_attributes(
- &'a mut self,
- attributes: HashMap<String, AttributeText<'a>>,
- ) -> &mut Self;
-
- fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
-}
-
impl<'a> NodeAttributes<'a> for NodeAttributeStatementBuilder<'a> {
fn add_attribute<S: Into<String>>(
&mut self,
@@ -1029,416 +881,6 @@ impl<'a> AttributeStatement<'a> for NodeAttributeStatement<'a> {
}
}
-pub trait EdgeAttributes<'a> {
- /// Style of arrowhead on the head node of an edge.
- /// This will only appear if the dir attribute is forward or both.
- fn arrow_head(&mut self, arrowhead: ArrowType) -> &mut Self {
- self.add_attribute("arrowhead", AttributeText::from(arrowhead))
- }
-
- // TODO: constrain
- /// Multiplicative scale factor for arrowheads.
- /// default: 1.0, minimum: 0.0
- fn arrow_size(&mut self, arrow_size: f32) -> &mut Self {
- self.add_attribute("arrowsize", AttributeText::from(arrow_size))
- }
-
- /// Style of arrowhead on the tail node of an edge.
- /// This will only appear if the dir attribute is back or both.
- fn arrowtail(&mut self, arrowtail: ArrowType) -> &mut Self {
- self.add_attribute("arrowtail", AttributeText::from(arrowtail))
- }
-
- /// Classnames to attach to the edge’s SVG element.
- /// Combine with stylesheet for styling SVG output using CSS classnames.
- /// Multiple space-separated classes are supported.
- fn class(&mut self, class: String) -> &mut Self {
- Attributes::class(self.get_attributes_mut(), class);
- self
- }
-
- /// Basic drawing color for graphics, not text. For the latter, use the fontcolor attribute.
- fn color(&mut self, color: Color<'a>) -> &mut Self {
- Attributes::color(self.get_attributes_mut(), color);
- self
- }
-
- fn color_with_colorlist(&mut self, color: ColorList<'a>) -> &mut Self {
- Attributes::color_with_colorlist(self.get_attributes_mut(), color);
- self
- }
-
- /// This attribute specifies a color scheme namespace: the context for interpreting color names.
- /// In particular, if a color value has form "xxx" or "//xxx", then the color xxx will be evaluated
- /// according to the current color scheme. If no color scheme is set, the standard X11 naming is used.
- /// For example, if colorscheme=bugn9, then color=7 is interpreted as color="/bugn9/7".
- fn color_scheme(&mut self, color_scheme: String) -> &mut Self {
- Attributes::color_scheme(self.get_attributes_mut(), color_scheme);
- self
- }
-
- /// Comments are inserted into output. Device-dependent
- fn comment(&mut self, comment: String) -> &mut Self {
- self.add_attribute("comment", AttributeText::attr(comment));
- self
- }
-
- /// If false, the edge is not used in ranking the nodes.
- fn constriant(&mut self, constriant: bool) -> &mut Self {
- self.add_attribute("constriant", AttributeText::from(constriant))
- }
-
- /// If true, attach edge label to edge by a 2-segment polyline, underlining the label,
- /// then going to the closest point of spline.
- fn decorate(&mut self, decorate: bool) -> &mut Self {
- self.add_attribute("decorate", AttributeText::from(decorate))
- }
-
- /// Edge type for drawing arrowheads.
- /// Indicates which ends of the edge should be decorated with an arrowhead.
- /// The actual style of the arrowhead can be specified using the arrowhead and arrowtail attributes.
- fn dir(&mut self, dir: Direction) -> &mut Self {
- self.add_attribute("dir", AttributeText::from(dir))
- }
-
- /// If the edge has a URL or edgeURL attribute, edgetarget determines which window
- /// of the browser is used for the URL attached to the non-label part of the edge.
- /// Setting edgetarget=_graphviz will open a new window if it doesn’t already exist,
- /// or reuse it if it does.
- fn edge_target(&mut self, edge_target: String) -> &mut Self {
- self.add_attribute("edgetarget", AttributeText::escaped(edge_target))
- }
-
- /// Tooltip annotation attached to the non-label part of an edge.
- /// Used only if the edge has a URL or edgeURL attribute.
- fn edge_tooltip(&mut self, edge_tooltip: String) -> &mut Self {
- self.add_attribute("edgetooltip", AttributeText::escaped(edge_tooltip))
- }
-
- /// The link for the non-label parts of an edge.
- /// edgeURL overrides any URL defined for the edge.
- /// Also, edgeURL is used near the head or tail node unless overridden by headURL or tailURL, respectively.
- fn edge_url(&mut self, edge_url: String) -> &mut Self {
- self.add_attribute("edgeurl", AttributeText::escaped(edge_url))
- }
-
- // TODO: color list
- /// Color used to fill the background of a node or cluster assuming style=filled, or a filled arrowhead.
- fn fill_color(&mut self, fill_color: Color<'a>) -> &mut Self {
- Attributes::fill_color(self.get_attributes_mut(), fill_color);
- self
- }
-
- // TODO: color list
- /// Color used for text.
- fn font_color(&mut self, font_color: Color<'a>) -> &mut Self {
- Attributes::font_color(self.get_attributes_mut(), font_color);
- self
- }
-
- /// Font used for text.
- fn font_name(&mut self, font_name: String) -> &mut Self {
- Attributes::font_name(self.get_attributes_mut(), font_name);
- self
- }
-
- /// Font size, in points, used for text.
- /// default: 14.0, minimum: 1.0
- fn font_size(&mut self, font_size: f32) -> &mut Self {
- Attributes::font_size(self.get_attributes_mut(), font_size);
- self
- }
-
- /// Position of an edge’s head label, in points. The position indicates the center of the label.
- fn head_lp(&mut self, head_lp: Point) -> &mut Self {
- self.add_attribute("head_lp", AttributeText::from(head_lp))
- }
-
- /// If true, the head of an edge is clipped to the boundary of the head node;
- /// otherwise, the end of the edge goes to the center of the node, or the center
- /// of a port, if applicable.
- fn head_clip(&mut self, head_clip: bool) -> &mut Self {
- self.add_attribute("headclip", AttributeText::from(head_clip))
- }
-
- /// Text label to be placed near head of edge.
- fn head_label(&mut self, head_label: String) -> &mut Self {
- self.add_attribute("headlabel", AttributeText::quoted(head_label))
- }
-
- /// Indicates where on the head node to attach the head of the edge.
- /// In the default case, the edge is aimed towards the center of the node,
- /// and then clipped at the node boundary.
- fn head_port(&mut self, head_port: PortPosition) -> &mut Self {
- self.add_attribute("headport", AttributeText::from(head_port))
- }
-
- /// If the edge has a headURL, headtarget determines which window of the browser is used for the URL.
- /// Setting headURL=_graphviz will open a new window if the window doesn’t already exist,
- /// or reuse the window if it does.
- /// If undefined, the value of the target is used.
- fn head_target(&mut self, head_target: String) -> &mut Self {
- self.add_attribute("headtarget", AttributeText::escaped(head_target))
- }
-
- /// Tooltip annotation attached to the head of an edge.
- /// Used only if the edge has a headURL attribute.
- fn head_tooltip(&mut self, head_tooltip: String) -> &mut Self {
- self.add_attribute("headtooltip", AttributeText::escaped(head_tooltip))
- }
-
- /// If defined, headURL is output as part of the head label of the edge.
- /// Also, this value is used near the head node, overriding any URL value.
- fn head_url(&mut self, head_url: String) -> &mut Self {
- self.add_attribute("headURL", AttributeText::escaped(head_url))
- }
-
- /// An escString or an HTML label.
- fn label(&mut self, label: String) -> &mut Self {
- Attributes::label(self.get_attributes_mut(), label);
- self
- }
-
- // TODO: constrain
- /// Determines, along with labeldistance, where the headlabel / taillabel are
- /// placed with respect to the head / tail in polar coordinates.
- /// The origin in the coordinate system is the point where the edge touches the node.
- /// The ray of 0 degrees goes from the origin back along the edge, parallel to the edge at the origin.
- /// The angle, in degrees, specifies the rotation from the 0 degree ray,
- /// with positive angles moving counterclockwise and negative angles moving clockwise.
- /// default: -25.0, minimum: -180.0
- fn label_angle(&mut self, label_angle: f32) -> &mut Self {
- self.add_attribute("labelangle", AttributeText::from(label_angle))
- }
-
- /// Multiplicative scaling factor adjusting the distance that the headlabel / taillabel is from
- /// the head / tail node.
- /// default: 1.0, minimum: 0.0
- fn label_distance(&mut self, label_distance: f32) -> &mut Self {
- self.add_attribute("labeldistance", AttributeText::from(label_distance))
- }
-
- /// If true, allows edge labels to be less constrained in position.
- /// In particular, it may appear on top of other edges.
- fn label_float(&mut self, label_float: bool) -> &mut Self {
- self.add_attribute("labelfloat", AttributeText::from(label_float))
- }
-
- /// Color used for headlabel and taillabel.
- fn label_font_color(&mut self, label_font_color: Color<'a>) -> &mut Self {
- self.add_attribute("labelfontcolor", AttributeText::from(label_font_color))
- }
-
- /// Font used for headlabel and taillabel.
- /// If not set, defaults to edge’s fontname.
- fn label_font_name(&mut self, label_font_name: String) -> &mut Self {
- self.add_attribute("labelfontname", AttributeText::attr(label_font_name))
- }
-
- // TODO: constrains
- /// Font size, in points, used for headlabel and taillabel.
- /// If not set, defaults to edge’s fontsize.
- /// default: 14.0, minimum: 1.0
- fn label_font_size(&mut self, label_font_size: f32) -> &mut Self {
- self.add_attribute("labelfontsize", AttributeText::from(label_font_size))
- }
-
- /// If the edge has a URL or labelURL attribute, this attribute determines
- /// which window of the browser is used for the URL attached to the label.
- fn label_target(&mut self, label_target: String) -> &mut Self {
- self.add_attribute("labeltarget", AttributeText::escaped(label_target))
- }
-
- /// Tooltip annotation attached to label of an edge.
- /// Used only if the edge has a URL or labelURL attribute.
- fn label_tooltip(&mut self, label_tooltip: String) -> &mut Self {
- self.add_attribute("labeltooltip", AttributeText::escaped(label_tooltip))
- }
-
- /// If defined, labelURL is the link used for the label of an edge.
- /// labelURL overrides any URL defined for the edge.
- fn label_url(&mut self, label_url: String) -> &mut Self {
- self.add_attribute("labelurl", AttributeText::escaped(label_url))
- }
-
- fn layer(&mut self, layer: String) -> &mut Self {
- Attributes::layer(self.get_attributes_mut(), layer);
- self
- }
-
- fn lhead(&mut self, lhead: String) -> &mut Self {
- self.add_attribute("lhead", AttributeText::quoted(lhead))
- }
-
- /// Label position
- /// The position indicates the center of the label.
- fn label_position(&mut self, lp: Point) -> &mut Self {
- Attributes::label_position(self.get_attributes_mut(), lp);
- self
- }
-
- /// Logical tail of an edge.
- /// When compound=true, if ltail is defined and is the name of a cluster
- /// containing the real tail, the edge is clipped to the boundary of the cluster.
- fn ltail(&mut self, ltail: String) -> &mut Self {
- self.add_attribute("ltail", AttributeText::quoted(ltail))
- }
-
- /// Minimum edge length (rank difference between head and tail).
- fn min_len(&mut self, min_len: u32) -> &mut Self {
- self.add_attribute("minlen", AttributeText::from(min_len))
- }
-
- fn no_justify(&mut self, no_justify: bool) -> &mut Self {
- self.add_attribute("nojustify", AttributeText::from(no_justify))
- }
-
- fn pen_width(&mut self, pen_width: f32) -> &mut Self {
- Attributes::pen_width(self.get_attributes_mut(), pen_width);
- self
- }
-
- /// Position of node, or spline control points.
- /// the position indicates the center of the node. On output, the coordinates are in points.
- fn pos(&mut self, pos: Point) -> &mut Self {
- Attributes::pos(self.get_attributes_mut(), pos);
- self
- }
-
- /// Edges with the same head and the same samehead value are aimed at the same point on the head.
- fn same_head(&mut self, same_head: String) -> &mut Self {
- self.add_attribute("samehead", AttributeText::quoted(same_head))
- }
-
- /// Edges with the same tail and the same sametail value are aimed at the same point on the tail.
- fn same_tail(&mut self, same_tail: String) -> &mut Self {
- self.add_attribute("sametail", AttributeText::quoted(same_tail))
- }
-
- // TODO: constrain
- /// Print guide boxes in PostScript at the beginning of routesplines if showboxes=1, or at the
- /// end if showboxes=2.
- /// (Debugging, TB mode only!)
- /// default: 0, minimum: 0
- fn show_boxes(&mut self, show_boxes: u32) -> &mut Self {
- Attributes::show_boxes(self.get_attributes_mut(), show_boxes);
- self
- }
-
- /// Set style information for components of the graph.
- fn style(&mut self, style: EdgeStyle) -> &mut Self {
- Attributes::style(self.get_attributes_mut(), Styles::Edge(style));
- self
- }
-
- /// Position of an edge’s tail label, in points.
- /// The position indicates the center of the label.
- fn tail_lp(&mut self, tail_lp: Point) -> &mut Self {
- self.add_attribute("tail_lp", AttributeText::from(tail_lp))
- }
-
- /// If true, the tail of an edge is clipped to the boundary of the tail node; otherwise,
- /// the end of the edge goes to the center of the node, or the center of a port, if applicable.
- fn tail_clip(&mut self, tail_clip: bool) -> &mut Self {
- self.add_attribute("tailclip", AttributeText::from(tail_clip))
- }
-
- /// Text label to be placed near tail of edge.
- fn tail_label(&mut self, tail_label: String) -> &mut Self {
- self.add_attribute("taillabel", AttributeText::quoted(tail_label))
- }
-
- /// Indicates where on the tail node to attach the tail of the edge.
- fn tail_port(&mut self, tail_port: PortPosition) -> &mut Self {
- self.add_attribute("tailport", AttributeText::from(tail_port))
- }
-
- /// If the edge has a tailURL, tailtarget determines which window of the browser is used for the URL.
- fn tail_target(&mut self, tail_target: String) -> &mut Self {
- self.add_attribute("tailtarget", AttributeText::escaped(tail_target))
- }
-
- /// Tooltip annotation attached to the tail of an edge.
- fn tail_tooltip(&mut self, tail_tooltip: String) -> &mut Self {
- self.add_attribute("tailtooltip", AttributeText::escaped(tail_tooltip))
- }
-
- /// If defined, tailURL is output as part of the tail label of the edge.
- /// Also, this value is used near the tail node, overriding any URL value.
- fn tail_url(&mut self, tail_url: String) -> &mut Self {
- self.add_attribute("tailURL", AttributeText::escaped(tail_url))
- }
-
- /// If the object has a URL, this attribute determines which window of the browser is used for the URL.
- fn target(&mut self, target: String) -> &mut Self {
- self.add_attribute("target", AttributeText::escaped(target))
- }
-
- /// Tooltip annotation attached to the node or edge.
- /// If unset, Graphviz will use the object’s label if defined.
- /// Note that if the label is a record specification or an HTML-like label,
- /// the resulting tooltip may be unhelpful.
- /// In this case, if tooltips will be generated, the user should set a tooltip attribute explicitly.
- fn tooltip(&mut self, tooltip: String) -> &mut Self {
- Attributes::tooltip(self.get_attributes_mut(), tooltip);
- self
- }
-
- /// Hyperlinks incorporated into device-dependent output.
- fn url(&mut self, url: String) -> &mut Self {
- Attributes::url(self.get_attributes_mut(), url);
- self
- }
-
- // TODO: contrain
- /// Weight of edge.
- /// The heavier the weight, the shorter, straighter and more vertical the edge is.
- /// default: 1, minimum: 0
- fn weight(&mut self, weight: u32) -> &mut Self {
- self.add_attribute("weight", AttributeText::attr(weight.to_string()))
- }
-
- /// External label for a node or edge.
- /// The label will be placed outside of the node but near it.
- /// These labels are added after all nodes and edges have been placed.
- /// The labels will be placed so that they do not overlap any node or label.
- /// This means it may not be possible to place all of them.
- /// To force placing all of them, set forcelabels=true.
- fn xlabel(&mut self, xlabel: String) -> &mut Self {
- Attributes::xlabel(self.get_attributes_mut(), xlabel);
- self
- }
-
- /// Position of an exterior label, in points.
- /// The position indicates the center of the label.
- fn xlp(&mut self, xlp: Point) -> &mut Self {
- Attributes::xlp(self.get_attributes_mut(), xlp);
- self
- }
-
- fn add_attribute<S: Into<String>>(
- &mut self,
- key: S,
- value: AttributeText<'a>,
- ) -> &mut Self;
-
- fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>>;
-
- // fn add_attribute<S: Into<String>>(
- // &self,
- // key: S,
- // value: AttributeText<'a>
- // ) {
- // self.get_attributes().insert(key.into(), value);
- // }
-
- // fn get_attributes(&self) -> IndexMap<String, AttributeText<'a>>;
-
- // fn get_attributes_mut(&self) -> &mut IndexMap<String, AttributeText<'a>>;
-
- // fn to_dot_string(&self) -> String;
-}
-
impl<'a> EdgeAttributes<'a> for EdgeAttributeStatementBuilder<'a> {
fn add_attribute<S: Into<String>>(
&mut self,
@@ -1504,3 +946,7 @@ impl<'a> EdgeAttributeStatement<'a> {
self
}
}
+
+fn get_indentation(indentation_level: usize) -> String {
+ INDENT.repeat(indentation_level)
+}
diff --git a/src/lib.rs b/src/lib.rs
index 2b64b9d..05ccfbe 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -46,12 +46,12 @@
//!
//! ```rust
//! use dotavious::attributes::{
-//! AttributeText, Color, CompassPoint, EdgeStyle, GraphAttributeStatementBuilder,
-//! GraphAttributes, NodeStyle, PortPosition, RankDir, Shape,
+//! AttributeText, Color, CompassPoint, EdgeAttributes, EdgeStyle, GraphAttributeStatementBuilder,
+//! GraphAttributes, NodeAttributes, NodeStyle, PortPosition, RankDir, Shape,
//! };
//! use dotavious::{
-//! Dot, Edge, EdgeAttributeStatementBuilder, EdgeAttributes, EdgeBuilder, Graph,
-//! GraphBuilder, Node, NodeAttributeStatementBuilder, NodeAttributes, NodeBuilder,
+//! Dot, Edge, EdgeAttributeStatementBuilder, EdgeBuilder, Graph,
+//! GraphBuilder, Node, NodeAttributeStatementBuilder, NodeBuilder,
//! };
//! use std::io;
//! use std::io::Read;
@@ -67,9 +67,8 @@ pub mod dot;
#[doc(hidden)]
pub use crate::dot::{
- Dot, DotString, Edge, EdgeAttributeStatementBuilder, EdgeAttributes, EdgeBuilder,
- Graph, GraphBuilder, Node, NodeAttributeStatementBuilder, NodeAttributes,
- NodeBuilder,
+ Dot, DotString, Edge, EdgeAttributeStatementBuilder, EdgeBuilder, Graph,
+ GraphBuilder, Node, NodeAttributeStatementBuilder, NodeBuilder,
};
// TODO: support adding edge based on index of nodes?
diff --git a/tests/dot.rs b/tests/dot.rs
index efafac8..526ff83 100644
--- a/tests/dot.rs
+++ b/tests/dot.rs
@@ -1,10 +1,12 @@
use dotavious::attributes::{
- AttributeText, Color, CompassPoint, EdgeStyle, GraphAttributeStatementBuilder,
- GraphAttributes, NodeStyle, PortPosition, RankDir, Shape,
+ AttributeText, Color, CompassPoint, EdgeAttributes, EdgeStyle,
+ GraphAttributeStatementBuilder, GraphAttributes, GraphStyle, NodeAttributes,
+ NodeStyle, PortPosition, RankDir, Shape,
};
+use dotavious::dot::SubGraphBuilder;
use dotavious::{
- Dot, Edge, EdgeAttributeStatementBuilder, EdgeAttributes, EdgeBuilder, Graph,
- GraphBuilder, Node, NodeAttributeStatementBuilder, NodeAttributes, NodeBuilder,
+ Dot, Edge, EdgeAttributeStatementBuilder, EdgeBuilder, Graph, GraphBuilder, Node,
+ NodeAttributeStatementBuilder, NodeBuilder,
};
use std::io;
use std::io::Read;
@@ -324,3 +326,99 @@ fn graph_attributes() {
"#
);
}
+
+#[test]
+fn clusters() {
+ let cluster_0 = SubGraphBuilder::new(Some("cluster_0".to_string()))
+ .add_graph_attributes(
+ GraphAttributeStatementBuilder::new()
+ .label("process #1".to_string())
+ .style(GraphStyle::Filled)
+ .color(Color::Named("lightgrey"))
+ .build(),
+ )
+ .add_node_attributes(
+ NodeAttributeStatementBuilder::new()
+ .style(NodeStyle::Filled)
+ .color(Color::Named("white"))
+ .build(),
+ )
+ .add_edge(Edge::new("a0".to_string(), "a1".to_string()))
+ .add_edge(Edge::new("a1".to_string(), "a2".to_string()))
+ .add_edge(Edge::new("a2".to_string(), "a3".to_string()))
+ .build();
+
+ let cluster_1 = SubGraphBuilder::new(Some("cluster_1".to_string()))
+ .add_graph_attributes(
+ GraphAttributeStatementBuilder::new()
+ .label("process #2".to_string())
+ .style(GraphStyle::Filled)
+ .color(Color::Named("blue"))
+ .build(),
+ )
+ .add_node_attributes(
+ NodeAttributeStatementBuilder::new()
+ .style(NodeStyle::Filled)
+ .build(),
+ )
+ .add_edge(Edge::new("b0".to_string(), "b1".to_string()))
+ .add_edge(Edge::new("b1".to_string(), "b2".to_string()))
+ .add_edge(Edge::new("b2".to_string(), "b3".to_string()))
+ .build();
+
+ let g = GraphBuilder::new_directed(Some("G".to_string()))
+ .add_node(
+ NodeBuilder::new("start".to_string())
+ .shape(Shape::Mdiamond)
+ .build(),
+ )
+ .add_node(
+ NodeBuilder::new("end".to_string())
+ .shape(Shape::Msquare)
+ .build(),
+ )
+ .add_sub_graph(cluster_0)
+ .add_sub_graph(cluster_1)
+ .add_edge(Edge::new("start".to_string(), "a0".to_string()))
+ .add_edge(Edge::new("start".to_string(), "b0".to_string()))
+ .add_edge(Edge::new("a1".to_string(), "b3".to_string()))
+ .add_edge(Edge::new("b2".to_string(), "a3".to_string()))
+ .add_edge(Edge::new("a3".to_string(), "a0".to_string()))
+ .add_edge(Edge::new("a3".to_string(), "end".to_string()))
+ .add_edge(Edge::new("b3".to_string(), "end".to_string()))
+ .build();
+
+ let r = test_input(g);
+
+ assert_eq!(
+ r.unwrap(),
+ r#"digraph G {
+ subgraph cluster_0 {
+ graph [label="process #1", style=filled, color="lightgrey"];
+ node [style=filled, color="white"];
+ a0 -> a1;
+ a1 -> a2;
+ a2 -> a3;
+ }
+
+ subgraph cluster_1 {
+ graph [label="process #2", style=filled, color="blue"];
+ node [style=filled];
+ b0 -> b1;
+ b1 -> b2;
+ b2 -> b3;
+ }
+
+ start [shape=Mdiamond];
+ end [shape=Msquare];
+ start -> a0;
+ start -> b0;
+ a1 -> b3;
+ b2 -> a3;
+ a3 -> a0;
+ a3 -> end;
+ b3 -> end;
+}
+"#
+ );
+}