draw2d icon indicating copy to clipboard operation
draw2d copied to clipboard

Draw2d very slow

Open jimmy29347 opened this issue 6 years ago • 19 comments

I'm drawing a tree recursively with connections. it takes about 7 seconds to draw 15 node in chrome. Is the paid version better in performance?

jimmy29347 avatar Mar 20 '19 22:03 jimmy29347

painting 15 nodes with draw2d aren't slow. maybe you paint everything again and again if you add one element? the example with more than 15 elements is quite fast.

https://freegroup.github.io/draw2d/galerie_shape_basic/index.html

freegroup avatar Apr 27 '19 11:04 freegroup

Hi @freegroup,

You did an awesome job and I am very thankful to you. It is working great but I am facing slowness when generating figures (Table) with lots of child nodes (columns). When I add around 10 columns, it takes 2-3 seconds to render the table which increases linearly when I add more columns.

Here is how I am generating Table.


const productSchema = {
  name: "Product",
  columns: [
    {
      type: "Column",
      name: "id",
      dataType: "int"
    },
    {
      type: "Column",
      name: "supplier_id",
      dataType: "int"
    },
  // some more columns, we can simply repeate/copy paste 
};

/* global $ eval */
import draw2d from "draw2d";
import Column from "./Column"; // Similar to Table, a Flex Grid with two Labels, one for name, one for type

const Table = draw2d.shape.layout.FlexGridLayout.extend({
export default draw2d.shape.layout.VerticalLayout.extend({
  NAME: "Table",
  init: function(attr = {}, setter, getter) {
    this._super(
      $.extend(
        {
          bgColor: "#cccccc",
          stroke: 0,
          width: 400
        },
        attr
      ),
      setter,
      getter
    );

    this.header = new draw2d.shape.layout.HorizontalLayout({
      stroke: 0,
      radius: 0,
      bgColor: "#316896",
      padding: 8,
      cssClass: "TableHeader",
      width: 400
    });

    this.tableNameLabel = new draw2d.shape.basic.Label({
      text: attr.name || "Table",
      fontColor: "#ffffff",
      stroke: 0,
      fontSize: 16,
      cssClass: "HeaderLabel",
      fontFamily: "Verdana",
      padding: 0
    });
    this.header.add(this.tableNameLabel);
    this.add(this.header, { row: 0, col: 0 });
    const _this = this;
    this.columns = attr.columns;
    (attr.columns || []).map(function(obj, j) {
      _this.add(new Column({ name: obj.name, dataType: obj.dataType }));
    });
    this.header.on("click", () => {
      this.toFront();
      this.getPorts(true).data.forEach(port => port.toFront());
    });
  },
  setName: function(name) {
    this.tableNameLabel.setText(name);

    return this;
  },

  addColumns: function(columns) {
    const _this = this;
    this.columns = columns;
    (columns || []).map(function(obj, j) {
      _this.add(new Column({ name: obj.name, dataType: obj.dataType }));
    });
  },
  /**
   * @method
   * Return an objects with all important attributes for XML or JSON serialization
   *
   * @returns {Object}
   */
  getPersistentAttributes: function() {
    const memento = this._super();
    memento.name = this.tableNameLabel.getText();
    memento.columns = [];
    this.children.each(function(i, e) {
      if (i > 0) {
        // skip the header of the figure
        memento.columns.push(e.figure.getPersistentAttributes());
      }
    });

    return memento;
  },
  setPersistentAttributes: function(memento) {
    this._super(memento);
    this.setName(memento.name);
    // this.addColumns(memento.columns);
    if (typeof memento.columns !== "undefined") {
      $.each(
        memento.columns,
        $.proxy(function(i, e) {
          const column = this.addColumn(e);
          column.setDimension(285, 32);
        }, this)
      );
    }
    return this;
  },
  addColumn: function(data) {
    const column = new Column(data);
    this.add(column);
    column.setCssClass("Column");
    return column;
  }
});

// Here is how I am generating Table;
const table = new Table({ name: 'TableName', columns: productSchema.columns });
const createTableCommand = new draw2d.command.CommandAdd(
      canvas,
      figure,
      x,
      y
    );
this.getCommandStack().execute(createTableCommand);

Let me know what I am doing wrong. Is there a batch process function like someFigure.addBatch([fig1, fig2, fig3, ..., fign]). In my case, how I can improve my performance.
Looking forward to your suggestions and feedback.

Thanks again :)

mzohaibqc avatar May 27 '19 20:05 mzohaibqc

I am seeing this issue also. I have nodes that are connected and the more node/connections that are added the rendering of the canvas slows down exponentially.

https://codepen.io/desean1625/pen/WqpXqK

desean1625 avatar Jun 21 '19 17:06 desean1625

@freegroup Further investigation it looks like the non linear increase in time is caused by the draw2d.shape.basic.Label and other text based objects.

desean1625 avatar Jun 25 '19 13:06 desean1625

The reason for that seems to be the text dimension calculation. Font handling is very slow....

the BBox calculation is far from perfect and I think this the the main reason for this performance issue

https://github.com/freegroup/draw2d/blob/master/src/shape/basic/Label.js#L511-L544

  getMinWidth: function () {
    if (this.shape === null) {
      return 0
    }

    if (this.cachedMinWidth === null) {
      this.cachedMinWidth = this.svgNodes.getBBox(true).width
        + this.padding.left
        + this.padding.right
        + 2 * this.getStroke()
    }

    return this.cachedMinWidth
  },

  /**
   *
   * This value is relevant for the interactive resize of the figure.
   *
   * @returns {Number} Returns the min. width of this object.
   */
  getMinHeight: function () {
    if (this.shape === null) {
      return 0
    }

    if (this.cachedMinHeight === null) {
      this.cachedMinHeight = this.svgNodes.getBBox(true).height
        + this.padding.top
        + this.padding.bottom
        + (2 * this.getStroke())
    }

    return this.cachedMinHeight
  },

freegroup avatar Mar 06 '20 23:03 freegroup

another hot spot https://github.com/freegroup/draw2d/blob/master/src/shape/basic/Label.js#L158

freegroup avatar Mar 06 '20 23:03 freegroup

@freegroup Do you have an update for this? How could I use Labels which doesn't use that BBox calculation so that the performance improved.

marvz73 avatar May 06 '20 15:05 marvz73

@desean1625 Do you have an update for draw2d Label slow performance? Im having this issue right now. I hope you could help me or give some advice.

marvz73 avatar May 08 '20 00:05 marvz73

Hello, I join the question, please help if you have a solution

e2sence avatar May 08 '20 06:05 e2sence

Hello, @mzohaibqc @freegroup.. I join the question. When I add around 10-15 columns, it takes 2-3 seconds or more.. Is there any solution?

onurozkardes avatar Jun 01 '20 14:06 onurozkardes

I also had the issue for adding multiple figures and connections on the graph. I created a custom function which adds the figures and connections and then repaints them instead of repainting it after each addition. Here's the code.

 canvas.addAll = function (figures) {
         for(var i = 0; i < figures.length; i++) {
            var figure = figures[i].figure, x = figures[i].x, y = figures[i].y;

            if (figure.getCanvas() === this) { return; }

            if (figure instanceof draw2d.shape.basic.Line) {
                this.lines.add(figure);
            } else {
                this.figures.add(figure);
                if (typeof y !== "undefined") {
                    figure.setPosition(x, y);
                } else if (typeof x !== "undefined") {
                    figure.setPosition(x);
                }
            }

            figure.setCanvas(this);

            // to avoid drag&drop outside of this canvas
            figure.installEditPolicy(this.regionDragDropConstraint);

            // important inital call
            figure.getShapeElement();

            // fire the figure:add event before the "move" event and after the figure.repaint() call!
            //   - the move event can only be fired if the figure part of the canvas.
            //     and in this case the notification event should be fired to the listener before
            this.fireEvent("figure:add", {figure: figure, canvas: this});

            // fire the event that the figure is part of the canvas
            figure.fireEvent("added", {figure: figure, canvas: this});

            // ...now we can fire the initial move event
            figure.fireEvent("move", {figure: figure, dx: 0, dy: 0});

            if (figure instanceof draw2d.shape.basic.PolyLine) {
                this.calculateConnectionIntersection();
            }
        }
        console.debug("Added all figures", performance.now());

        console.debug("Repainting figures", performance.now());
        this.figures.each(function(i, fig){
            fig.repaint();
        });
        console.debug("Repainted figures", performance.now());

        console.debug("Repainting lines", performance.now());
        this.lines.each(function (i, line) {
            line.svgPathString = null;
            line.repaint();
        });
        console.debug("Repainted lines", performance.now());
        return this;
    };

vijay1903 avatar Jun 05 '20 12:06 vijay1903

@vijay1903 @onrozkrds Guys, I am sorry for you that you invested learning this and finally came to know that this is super slow, same happened to me but it's never late to try something else.

I tried FabricJs, which is fast and api is clean and simple. so have a look, it may help

http://fabricjs.com/demos/

mzohaibqc avatar Jun 05 '20 18:06 mzohaibqc

@mzohaibqc this library out of the box can handle ports and connections. I think this is the main reason "to spend time learning it"

e2sence avatar Jun 05 '20 19:06 e2sence

@freegroup So what is the issue with text and label rendering and how can it be fixed/avoided? I have vertical layout objects that use between 10 and 15 labels for data and buttons etc. What is the alternative?
If I have just 3 or more objects on the canvas it is really slow when loading the page and rendering the objects initially.

gurnard avatar Sep 03 '20 12:09 gurnard

@mzohaibqc We don't always have a choice to choose a library. Sometimes, we have to work with what we have. Especially when you have a running program.

vijay1903 avatar Sep 03 '20 13:09 vijay1903

so no news on the performance of objects with text labels?

gurnard avatar Sep 17 '20 14:09 gurnard

Any update for this , if we have more than 100 node and conections, it will take more than 20 second from my PC

GiKyouGetsu avatar Sep 19 '21 13:09 GiKyouGetsu

I have same problem with vertical layout. Every label which i added to vertical layout cause to the performance issue.

RasadHasanzada avatar Dec 23 '22 06:12 RasadHasanzada

Any updates on this? Anyway to help solve the issue?

p3root avatar Oct 05 '23 06:10 p3root