How to order nodes inside a subgraph in dot - graphviz

I can't get the nodes to appear in the same order I wrote them
Here is the code I'm trying to use with dot:
digraph G {
fontname = "Hilda 10"
rankdir=LR
splines=line
nodesep=.08;
ranksep=1;
edge [color=black, arrowsize=.5];
node [fixedsize=true,style=filled,color=none,fillcolor=gray,shape=circle,fontcolor=black]
subgraph cluster_0 {
color=none;
node [style=filled, color=white, penwidth=15,fillcolor=gray shape=circle];
{ l10 l11 }
label = Input;
}
subgraph cluster_1 {
color=none;
node [style=filled, color=white, penwidth=15,fillcolor=gray shape=circle];
{ l20 l21 l22 }
label = Output;
}
l10 -> l20;
l10 -> l21;
l10 -> l22;
l11 -> l20;
l11 -> l21;
l11 -> l22;
}
In my second column I was hoping to get l20 on top followed by l21 and l22 on the bottom, but instead I'm getting the image below, with l21 on top and l20 on the bottom
Result
I also tried with:
rankdir=TB
...
rank=same
l20 -> l21 -> l22 [style = invis]
...
edge [constraint=false]
l10 -> l20;
l10 -> l21;
l10 -> l22;
l11 -> l20;
l11 -> l21;
l11 -> l22;
but this way the first column loses its vertical centering
2nd try

A really annoying problem. There are already MRs against this problem. (search https://gitlab.com/graphviz/graphviz/-/issues for "order")
Here is a solution, but it requires changing the ranking and then adding one invisible node (plus other tweaks).
digraph G {
fontname = "Hilda 10"
// rankdir=LR // change ranking
splines=line
ranksep=.05
nodesep=.05
edge [color=black, arrowsize=.5];
node [fixedsize=true,style=filled,color=none,fillcolor=gray,shape=circle,fontcolor=black]
subgraph cluster_0 {
color=none;
node [style=filled, color=white, penwidth=15,fillcolor=gray shape=circle];
{
// add an inviz node to drop nodes down 1 rank
spacer1 [label="" style=invis]
spacer1 -> l10 [minlen=1 style=invis]
edge [style=invis minlen=2] // 2 ranks between
l10 -> l11
}
label = Input;
}
subgraph cluster_1 {
color=none;
node [style=filled, color=white, penwidth=15,fillcolor=gray shape=circle];
{ XXrank=same
XXordering=out
edge [style=invis]
edge[minlen=2]
l20 -> l21 -> l22
}
label = Output;
}
edge[constraint=false]
l10 -> l20;
l10 -> l21;
l10 -> l22;
l11 -> l20;
l11 -> l21;
l11 -> l22;
}
Giving:

Related

DOT: How to connect to nodes when one node is within a subgraph

I have the following graph:
digraph D {
node [
fontname = "Arial"
fontsize = 11
shape = "record"
]
A [ label = "my_A" ]
B [ label = "my_B" ]
subgraph cluster_mg {
label = "Main Group";
penwidth = 1
color = "black"
subgraph cluster_subgroup1 {
color = "black";
style = dashed;
label = "Subgroup 1"
A
}
subgraph cluster_subgroup2 {
color = "white"
penwidth = 0;
label = "Subgroup 2"
B
}
}
#A -> B
#{ rank=same A B }
}
With the last two lines commented out, it produces this figure:
I want to add an arrow from my_A to my_B. When I do this (by uncommenting the first commented out line) my_A and my_B are placed vertically instead of horizontally. When I set their rank to be the same, I lose the dashed outline. How can I construct the same figure as displayed here, but with a line from my_A to my_B?
Also, how can I put my_A on the left and my_B on the right?
Adding
rankdir = LR;
to the graph[] portion did what I needed:
digraph D {
graph [ rankdir = LR ]
node [
fontname = "Arial"
fontsize = 11
shape = "record"
]
A [ label = "my_A" ]
B [ label = "my_B" ]
subgraph cluster_mg {
label = "Main Group";
penwidth = 1
color = "black"
subgraph cluster_subgroup1 {
color = "black";
style = dashed;
label = "Subgroup 1"
A
}
subgraph cluster_subgroup2 {
color = "white"
penwidth = 0;
label = "Subgroup 2"
B
}
}
A -> B
}

Processing 3.5.3 Rotating points around center point Image shrinking in size and dissapears

My problem is as in the title. I am trying to write a simple game in processing with a car that you can drive on a 2D plane. I wanted to create a rotation of the car since it seems crucial so I did it as described here:Rotating points in 2D
But my implementation seems to fail a bit. You see, when I hit left of right arrow the car actually rotates but shrinks in size as it is rotating and after few turns it completely dissapears. Can you show me what am I missing here? Thanks in advance! Code of my functions:
class Point
{
float x, y;
Point(float xx, float yy)
{
x = xx;
y = yy;
}
Point()
{
x = y = 0.0;
}
void Rotate(Point center, float angle)
{
float s = sin(angle);
float c = cos(angle);
y = center.y + ((y-center.y) * c + (x-center.x) * s);
x = center.x + ((x-center.x) * c - (y-center.y) * s);
}
}
class Car
{
Point LT;
Point RT;
Point LB;
Point RB;
Point center;
float r;
float acceleration;
Car()
{
LT = new Point(10, 10);
RT = new Point (30, 10);
LB = new Point(10, 50);
RB = new Point(30, 50);
r = sqrt(pow(15-30, 2) + pow(25-10, 2));
}
Car(Point lt, Point rt, Point lb, Point rb)
{
LT = lt;
RT = rt;
LB = lb;
RB = rb;
center = new Point(abs((LT.x - RT.x)/2), abs((LT.y - LB.y)/2));
r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
}
Car(Point Center, float w, float h)
{
center = Center;
LT = new Point(center.x - w/2, center.y - h/2);
RT = new Point (center.x + w/2, center.y - h/2);
LB = new Point(center.x - w/2, center.y + h/2);
RB = new Point(center.x + w/2, center.y + h/2);
r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
}
void Show()
{
fill(45, 128, 156);
beginShape();
vertex(LT.x, LT.y);
vertex(RT.x, RT.y);
vertex(RB.x, RB.y);
vertex(LB.x, LB.y);
endShape();
}
void Update()
{
}
void Turn(float angle)
{
LT.Rotate(center, angle);
RT.Rotate(center, angle);
RB.Rotate(center, angle);
LB.Rotate(center, angle);
}
void Accelerate(float accel)
{
}
}
In main I only use car.Show() and I turn by -0.1 per left cliock and 0.1 per right click
EDIT
If you want to see whole code visit my github repo
Unfortunately I can't explain more at the moment, but here's a simpler option using one of the formulas you've pointed to:
Car car = new Car();
void setup(){
size(300,300);
// this helps draw rectangles from centre (as opposed to corner (default))
rectMode(CENTER);
car.position.set(150,150);
}
void draw(){
background(255);
if(keyPressed){
if(keyCode == UP){
car.speed = 1;
}
}
car.draw();
}
void keyPressed(){
if(keyCode == LEFT){
car.steer -= radians(10);
}
if(keyCode == RIGHT){
car.steer += radians(10);
}
}
void keyReleased(){
if(keyCode == UP){
car.speed = 0;
}
}
class Car{
PVector position = new PVector();
PVector velocity = new PVector();
float speed;
float steer;
void update(){
// use the same polar to cartesian coordinates formulate for quick'n'dirty steering
velocity.set(cos(steer) * speed,sin(steer) * speed);
// update position based on velocity
position.add(velocity);
}
void draw(){
update();
// use a nested coordinate system to handle translation and rotation for us
// order of operations is important
pushMatrix();
translate(position.x,position.y);
rotate(steer);
rect(0,0,30,15);
popMatrix();
}
}
Update
The main issue with points shrinking is you're cumulatively transforming the points when you rotate them. After each transformation there is no history of what the x,y were. Instead you should return a new point that is transformed, thus "remembering" the old x,y position.
Bellow is a tweaked version of your code, minus the two constructor variants.
Hopefully the comments will help:
Car car = new Car();
void setup(){
size(300,300);
}
void draw(){
if(keyCode == UP){
if(keyPressed){
car.Accelerate(1);
}else{
car.Accelerate(0);
}
}
car.Update();
background(255);
car.Show();
}
void keyPressed(){
if(keyCode == LEFT){
car.Turn(radians(-3));
}
if(keyCode == RIGHT){
car.Turn(radians(+3));
}
}
class Point
{
float x, y;
Point(float xx, float yy)
{
x = xx;
y = yy;
}
Point()
{
x = y = 0.0;
}
Point Rotate(Point center, float angle)
{
float s = sin(angle);
float c = cos(angle);
// return a new point (a rotated copy), rather than overwriting this one
return new Point(center.x + ((x-center.x) * c - (y-center.y) * s),
center.y + ((y-center.y) * c + (x-center.x) * s));
}
// translate by another point
void AddToSelf(Point point){
this.x += point.x;
this.y += point.y;
}
// pretty print info when using println()
String toString(){
return "[Point x=" + x + " y="+ y +"]";
}
}
class Car
{
Point LT;
Point RT;
Point LB;
Point RB;
Point center;
float r;
float acceleration;
// car angle: used to compute velocity and update vertices
float angle;
// car position: used to offset rendering position of the corners
Point position;
// car velocity: amount by which position translates
Point velocity = new Point();
Car()
{
float x = 10;
float y = 10;
float w = 40;
float h = 20;
// setup corners with no translation
LT = new Point(0 , 0 );
RT = new Point(0 + w, 0 );
LB = new Point(0 , 0 + h);
RB = new Point(0 + w, 0 + h);
// setup initial position
position = new Point(x,y);
center = new Point(w / 2, h / 2);
r = sqrt(pow(15-30, 2) + pow(25-10, 2));
}
//Car(Point lt, Point rt, Point lb, Point rb)
//{
// LT = lt;
// RT = rt;
// LB = lb;
// RB = rb;
// center = new Point(abs((LT.x - RT.x)/2), abs((LT.y - LB.y)/2));
// r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
//}
//Car(Point Center, float w, float h)
//{
// center = Center;
// LT = new Point(center.x - w/2, center.y - h/2);
// RT = new Point (center.x + w/2, center.y - h/2);
// LB = new Point(center.x - w/2, center.y + h/2);
// RB = new Point(center.x + w/2, center.y + h/2);
// r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
//}
void Show()
{
fill(45, 128, 156);
beginShape();
// render corners offset by the car position
vertex(position.x + LT.x, position.y + LT.y);
vertex(position.x + RT.x, position.y + RT.y);
vertex(position.x + RB.x, position.y + RB.y);
vertex(position.x + LB.x, position.y + LB.y);
endShape(CLOSE);
}
void Update()
{
// update velocity based on car angle and acceleration
velocity.x = cos(angle) * acceleration;
velocity.y = sin(angle) * acceleration;
// update position based on velocity
position.AddToSelf(velocity);
}
void Turn(float angle)
{
this.angle += angle;
// replace the old point with the transformed points
// (rather than continuosly transforming the same point)
LT = LT.Rotate(center, angle);
RT = RT.Rotate(center, angle);
RB = RB.Rotate(center, angle);
LB = LB.Rotate(center, angle);
}
void Accelerate(float accel)
{
acceleration = accel;
}
}

JavaFX Marquee go out of my node

I have a issue with my Marquee animation with JavaFX. I have a HBox with three Nodes and in the second node I have a Text node inside that I need do the Marquee transformation, but when the text goes out of the second node I need it doesn't be visible.
I'll go to set a picture to show my issue (the text is visible in the white area).
My Hbox code:
HBox bill = new HBox(0);
bill.getChildren().addAll(logoPane,product,total);
bill.setBackground(new Background(new BackgroundFill(Color.web("#FFFFFF"), CornerRadii.EMPTY, Insets.EMPTY)));
bill.setHgrow(product, Priority.ALWAYS);
Animation:
timelineAnimation = new Timeline();
final KeyValue kv = new KeyValue(productLabel.translateXProperty(), -1000);
final KeyFrame kf = new KeyFrame(Duration.millis(2000), kv);
timelineAnimation.getKeyFrames().add(kf);
And how I define my product node:
productLabel.setFont(new Font("Times New Roman",30));
product = new StackPane();
product.setMaxWidth(2000);
product.setMaxHeight(100);
product.setMinWidth(574);
product.setMinHeight(100);
product.getChildren().add(productLabel);
product.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));
product.setAlignment(productLabel, Pos.CENTER);
Hope it was enough information.
Thanks!
Simply add a Rectangle as clip for the product pane and bind it's size to the size of the pane:
Rectangle clip = new Rectangle();
product.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
clip.setWidth(newValue.getWidth());
clip.setHeight(newValue.getHeight());
});
product.setClip(clip);
This will make sure no descendants of product are drawn outside the bounds of this node.

Masonry UITableview Cell : Autolayout errors for multiline label

I am learning auto layout in iOS8 by using SnapKit . I got lot of errors while applying constraints to cell subviews. below is the code used as subview to cell.contentview.
class FanFeedDynamicCellView: UIView{
var fanProfileImageView:UIImageView?
var fanNameLabel:UILabel?
var contentLabel:UILabel?
var thumbnailImageView:UIImageView?
var spacierView_FanProfile:UIView?
override init(frame: CGRect) {
super.init(frame : frame)
setupViewProperties()
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func setupViewProperties()
{
//1 add fanProfileImageView
fanProfileImageView = UIImageView()
fanProfileImageView!.image = UIImage(named: "avatar")
// setBorder(fanProfileImageView!)
self.addSubview(fanProfileImageView!)
//2 add Fan Name Label
fanNameLabel = UILabel()
fanNameLabel!.lineBreakMode = .ByTruncatingTail
fanNameLabel!.numberOfLines = 1
fanNameLabel!.textAlignment = .Left
fanNameLabel!.textColor = UIColor.blackColor()
fanNameLabel!.backgroundColor = UIColor(red: 0, green: 0, blue: 1, alpha: 0.1) // light blue
self.addSubview(fanNameLabel!)
//3 add ContentLabel
contentLabel = UILabel()
contentLabel!.lineBreakMode = .ByTruncatingTail
contentLabel!.numberOfLines = 0
contentLabel!.textAlignment = .Left
contentLabel!.textColor = UIColor.darkGrayColor()
contentLabel!.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.1) // light red
self.addSubview(contentLabel!)
//4 add Thumbnail View
thumbnailImageView = UIImageView()
// setBorder(thumbnailImageView!)
thumbnailImageView!.contentMode = .ScaleAspectFit
thumbnailImageView!.image = UIImage(named: "avatar")
self.addSubview(thumbnailImageView!)
updateFonts()
//Constraints for subviews
//setupConstraintsForProperties()
}
func updateFonts()
{
fanNameLabel!.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
contentLabel!.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption2)
}
override func updateConstraints()
{
let padding:UIEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
fanProfileImageView!.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.snp_top).offset(padding.top)
make.left.equalTo(self.snp_left).offset(padding.left)
make.width.height.equalTo(60.0)
make.bottom.lessThanOrEqualTo(thumbnailImageView!.snp_top).offset(-padding.bottom)
}
fanNameLabel!.snp_makeConstraints { (make) -> Void in
make.top.equalTo(self.snp_top).offset(padding.top)
make.left.equalTo(fanProfileImageView!.snp_right).offset(padding.right)
make.right.equalTo(self.snp_right).offset(-padding.right)
// make.bottom.lessThanOrEqualTo(contentLabel!.snp_top).offset(-padding.top)
make.height.equalTo(20)
}
contentLabel!.snp_makeConstraints { (make) -> Void in
make.top.equalTo(fanNameLabel!.snp_bottom).offset(padding.top)
make.left.equalTo(fanProfileImageView!.snp_right).offset(padding.left)
make.right.equalTo(self.snp_right).offset(-padding.right)
// make.bottom.lessThanOrEqualTo(thumbnailImageView!.snp_top).offset(-padding.bottom)
// make.height.greaterThanOrEqualTo(20)
}
thumbnailImageView!.snp_makeConstraints { (make) -> Void in
make.top.greaterThanOrEqualTo(contentLabel!.snp_bottom).offset(padding.top)
// make.left.equalTo(padding.left)
make.bottom.lessThanOrEqualTo(-padding.bottom)
make.height.greaterThanOrEqualTo(20)
make.centerX.equalTo(self.snp_centerX) }
super.updateConstraints()
}
func setBorder(cView:UIView) -> UIView
{
let cLayer : CALayer = cView.layer
cLayer.borderColor = UIColor.redColor().CGColor
cLayer.borderWidth = 0.5
return cView
}
override func layoutSubviews() {
super.layoutSubviews()
fanNameLabel!.contentHuggingPriorityForAxis(.Vertical)
fanNameLabel!.contentCompressionResistancePriorityForAxis(.Vertical)
contentLabel!.contentHuggingPriorityForAxis(.Vertical)
contentLabel!.contentCompressionResistancePriorityForAxis(.Vertical)
}
The output would be the same as attached image . here we use on profile Image in LeftSide . User Name on top of label. Content label marked in light orange color would be the multiline. below this i attached the ImageView. when i scroll the tableview the height of the cell is unpredictable(layout changes automatically). would help me to correct the constraint to achieve the this output. For first launch the multiline cell would be in One line . once i goes to invisible the come again visible it adopt for full label content
You add more constraints than you had to. Also you don't really need to use contentHuggingPriority and contentCompressionResistance to achieve what you want.
This is how to make it work:
FanFeedDynamicCellView
class FanFeedDynamicCellView: UIView {
let fanProfileImageView = UIImageView()
let fanNameLabel = UILabel()
let contentLabel = UILabel()
let thumbnailImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
setupConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
fanProfileImageView.image = UIImage(named: "avatar")
addSubview(fanProfileImageView)
fanNameLabel.lineBreakMode = .ByTruncatingTail
fanNameLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
fanNameLabel.numberOfLines = 1
fanNameLabel.textAlignment = .Left
fanNameLabel.textColor = UIColor.blackColor()
fanNameLabel.backgroundColor = UIColor(red: 0, green: 0, blue: 1, alpha: 0.1) // light blue
addSubview(fanNameLabel)
contentLabel.lineBreakMode = .ByTruncatingTail
contentLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption2)
contentLabel.numberOfLines = 0
contentLabel.textAlignment = .Left
contentLabel.textColor = UIColor.darkGrayColor()
contentLabel.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.1) // light red
addSubview(contentLabel)
thumbnailImageView.contentMode = .ScaleAspectFit
thumbnailImageView.image = UIImage(named: "thumbnail.jpg")
thumbnailImageView.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.2) // light green
addSubview(thumbnailImageView)
}
func setupConstraints() {
let padding:UIEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10)
fanProfileImageView.snp_makeConstraints { (make) -> Void in
make.top.equalTo(padding.top)
make.left.equalTo(padding.left)
make.width.height.equalTo(60)
make.bottom.lessThanOrEqualTo(-padding.bottom)
}
fanNameLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(fanProfileImageView)
make.left.equalTo(fanProfileImageView.snp_right).offset(padding.right)
make.right.equalTo(-padding.right)
}
contentLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(fanNameLabel.snp_bottom).offset(padding.top)
make.left.right.equalTo(fanNameLabel)
}
thumbnailImageView.snp_makeConstraints { (make) -> Void in
make.top.greaterThanOrEqualTo(contentLabel.snp_bottom).offset(padding.top)
make.top.greaterThanOrEqualTo(fanProfileImageView.snp_bottom).offset(padding.top)
make.left.equalTo(fanProfileImageView)
make.right.equalTo(fanNameLabel)
make.bottom.equalTo(-padding.bottom)
}
}
}
I don't know why you created a UIView subclass instead of a UITableViewCell subclass for this, but I kept it that way and just added this view to a custom UITableViewCell subclass
FanFeedTableViewCell
This only adds the FanFeedDynamicCellView to its contentView:
class FanFeedTableViewCell: UITableViewCell {
let fanFeedView = FanFeedDynamicCellView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
contentView.addSubview(fanFeedView)
fanFeedView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(contentView)
}
}
}
And that's it! Now just use this FanFeedTableViewCell in your UITableView and set the texts and images in cellForRowAtIndexPath.
Don't forget to do this with your table view to enable dynamic cell heights:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100 // or whatever your estimated row height is. This does not have to be precise
This is how it looks:

JavaFX: animation which uses PathTransition as a drawing pen

sample code
//node
Rectangle rect = new Rectangle (0, 0, 20, 20);
//path
Text text = TextBuilder.create()
.text("J a v a F X R o c k s")
.font(new Font(50))
.x(65)
.y(100)
.build();
// path transition
pathTransition = PathTransitionBuilder.create()
.duration(Duration.seconds(15))
.path(text)
.node(rect)
.orientation(OrientationType.ORTHOGONAL_TO_TANGENT)
.cycleCount(Timeline.INDEFINITE)
.autoReverse(true)
.build();
I want to display part of the the text(path) that traveled by rect node. Meaning in the above figure as rect node traveled until java, i want to be display that part only at that point of time ..
You can try to assign a clipping area to the Text and update it during animation:
public void start(Stage primaryStage) {
final Rectangle pen = new Rectangle(0, 0, 20, 20);
// this pane this contain clipping
final Pane clip = new Pane();
// listener to update clipping area
ChangeListener changeListener = new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
Rectangle newrect = new Rectangle(pen.getTranslateX(), pen.getTranslateY(), pen.getWidth(), pen.getHeight());
newrect.setRotate(pen.getRotate());
clip.getChildren().add(newrect);
}
};
// rect coordinates will be changed during animation, so we will listen to them
pen.translateXProperty().addListener(changeListener);
pen.translateYProperty().addListener(changeListener);
pen.rotateProperty().addListener(changeListener);
final Text text = TextBuilder.create()
.text("J a v a F X R o c k s")
.font(new Font(50))
.clip(clip)
.x(65)
.y(100)
.build();
PathTransition pathTransition = PathTransitionBuilder.create()
.duration(Duration.seconds(15))
.path(text)
.node(pen)
.orientation(OrientationType.ORTHOGONAL_TO_TANGENT)
.build();
// once we done we don't want to store thousands of rectangles used to clip
pathTransition.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
text.setClip(null);
clip.getChildren().clear();
}
});
Pane root = new Pane();
root.getChildren().addAll(text, pen);
primaryStage.setScene(new Scene(root, 600, 200));
primaryStage.show();
pathTransition.play();
}
A bit more efficient way to store clipping area can be Canvas object, but it will require a bit of math to draw rectangles with rotation on canvas, so it's your call.

Resources