Diagram Drawing

I want to share a video about my diagram drawing. It can be many shape types (circle, square, diamon or round square) and can connect two shapes with a straight line or spline. Right now, it doesn’t provide editor. Here is the sample code

    s = Shape('IT Department',100,200,ROUNDRECT,color(0,255,0,255),color(54, 184, 20, 255))
    c = Shape('Users Acceptance',300,400,CIRCLE,color(255,0,0,255))
    l = Connector(s,4,c,1)
    n= Shape('IT Success',600,600,RECT,color(255,0,255,255))
    l = Connector(c,2,n,3)
    d= Shape('Performance',600,300,DIAMON,color(0,255,255,255))
    l = Connector(n,2,d,3)
    g= Shape('IT Growth',700,300,DIAMON,color(255,255,0,255))
    l = Connector(n,2,g,3)

http://www.youtube.com/watch?v=PwXN1WvqExg&feature=youtube_gdata_player

Edit : To drag a line for creating a connector, you have to click the shape and it will show four dots. Drag a line from the dot to the target shape.



To move the shape, click the shape and drag to move the shape.



By default, the line style is Arrow-End style. You can change to ARROW_NONE, ARROW_START or ARROW_BOTH.

The line type by default is LINE_CURVE which uses the spline.

this is genius! It sounds super useful

Are you planning on adding an editor? This is amazing! Id be willing to team up if you don’t feel like doing it yourself.

Hi Sanit,

Very impressive - shades of mind mapping or flow sheeting. The routines could be very useful. Have you thought about having handles on the on jets - I’ve used LabView which uses objects linked in this fashion to build up applications. Building a language like that would be great to use and a good teaching tool.

Bri_G

:slight_smile:

@Zoyt I would like to add an editor as Spritely but it will take a lot of time for me. I will post the code here. It still has bugs and lacks a lot of functionality.

@Bri_G Thanks I used to use the Connector on Squeak created by Ned Konz. I was very surprised how he created the Connector.

Thank you for coding from this forum

Splines by @Nat

RoundRect by @Andrew_Stacey

Sorry for no coding comment.

Enjoin with it!

--main

-- Use this function to perform your initial setup

function setup()
    displayMode(FULLSCREEN)
    s = Shape('IT Department',100,200,ROUNDRECT,color(0,255,0,255),color(54, 184, 20, 255))
    c = Shape('Users Acceptance',300,400,CIRCLE,color(255,0,0,255))
    l = Connector(s,4,c,1)
    n= Shape('IT Success',600,600,RECT,color(255,0,255,255))
    l = Connector(c,2,n,3)
    d= Shape('Performance',600,300,DIAMON,color(0,255,255,255))
    l = Connector(n,2,d,3)
    g= Shape('IT Growth',700,300,DIAMON,color(255,255,0,255))
    l = Connector(n,2,g,3)
    --[[
    --You can save drawing in local data by double touch on screen
    --then you read it back by calling below code
    drawdata = loadstring(readLocalData('DrawData'))
    a = drawdata()
    --]]
end

-- This function gets called once every frame
function draw()
    background(255, 255, 255, 255)
    World():draw()
end

function touched(touch)
    World():touched(touch)
end
--World

NEW_LINE = 1
_substances = {}
_mode = nil
_startobj = nil 
_startpoint = nil
_touchHandle = false

World = class()

function World:init(x)
    -- you can accept and set parameters here

end

function World:add(s)
    -- you can accept and set parameters here
    table.insert(_substances,s)
end

function World:draw()
    -- Codea does not automatically call this method
    for i,v in pairs(_substances) do
        v:draw()
    end 
    if _mode == NEW_LINE then
        if CurrentTouch.state==MOVING then
            strokeWidth(5)
            stroke(0)
            line(_startobj:getPoint(_startpoint).x,_startobj:getPoint(_startpoint).y,
                        CurrentTouch.x,CurrentTouch.y)
        elseif CurrentTouch.state==ENDED then
            _mode = nil
            _startobj = nil
            _startpoint= nil
        end 
    end    
end

function World:touched(touch)
    -- Codea does not automatically call this method
    _touchHandle = false
    for i,v in pairs(_substances) do
        v:touched(touch)
        if _touchHandle then
            break
        end
    end
    if touch.tapCount==2 and touch.state == ENDED then
        self:save()
    end
end

function World:save()
    -- Codea does not automatically call this method
    clearOutput()
    local cmd = ''
    sp={}
    for i,v in ipairs(_substances) do
        if v.type== LINE then
            cmd = cmd.."s"..i.." = Connector("..sp[v.shape1]..","
                        ..v.point1..","..sp[v.shape2]..","
                        ..v.point2..")\
"
        else
            local c = v.color
            local fc = v.fillColor
            cmd = cmd.."s"..i.." = Shape('"..v.label.."',"..
                  v.x..","..v.y..","..v.type..",color("..c.r..
                  ","..c.g..","..c.b..","..c.a..")"..
                  ",color("..fc.r..","..fc.g..","..fc.b..","..fc.a.."))\
"
            sp[v] = "s"..i
        end
    end
    saveLocalData('DrawData',cmd)
    print("Data are saved completely.")
end

--RoundRect
function roundRect(x,y,w,h,r)
    pushStyle()
    
    insetPos = vec2(x+r,y+r)
    insetSize = vec2(w-2*r,h-2*r)
    
    --Copy fill into stroke
    stroke(stroke())
    fill(fill())
    noSmooth()
    rectMode(CORNER)
    rect(insetPos.x,insetPos.y,insetSize.x,insetSize.y)
    
    if r > 0 then
        smooth()
        lineCapMode(ROUND)
        strokeWidth(r*2)

        line(insetPos.x, insetPos.y, 
             insetPos.x + insetSize.x, insetPos.y)
        line(insetPos.x, insetPos.y,
             insetPos.x, insetPos.y + insetSize.y)
        line(insetPos.x, insetPos.y + insetSize.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)
        line(insetPos.x + insetSize.x, insetPos.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)            
    end
    popStyle()
end
--Shape

RECT = 1
ROUNDRECT = 2
CIRCLE = 3
DIAMON = 4
ELLIPSE = 5
SQUARE = 6
NORMAL = 1
EDIT = 2

Shape = class()

function Shape:init(label,x,y,t,c,fc)
    self.x = x
    self.y = y
    self.label = label
    self.font = "GillSans"
    self.fontSize = 16
    self.textColor = color(0,0,0,255)
    self.width = 65
    self.height = 65
    self.color = c or color(0,255,0,255)
    self.fillColor = fc or color(self.color.r,self.color.g,self.color.b,100)
    self.type = t or RECT
    self.mode = NORMAL
    World():add(self)
end

function Shape:draw()
    pushStyle()
        --determine width and height of the shape
        font(self.font)
        fontSize (self.fontSize)
        self.width,self.height = textSize(self.label)
        if self.width >= self.height then
            self.width = self.width + 20
            self.height = self.width
        else
            self.height = self.height + 20
            self.width = self.height
        end
        if self.height< 65 or self.width <65 then
            self.width = 65
            self.height = 65
        end
        strokeWidth(5)
        stroke(self.color)
        fill(self.fillColor)
        if self.type == RECT then
            rectMode(CENTER)
            rect(self.x,self.y,self.width,self.height)
        elseif self.type == ROUNDRECT then
            roundRect(self.x-self.width/2,self.y-self.height/2,self.width,self.height,40)
            stroke(self.fillColor)
            roundRect(self.x-self.width/2+3,self.y-self.height/2+3,self.width-6,self.height-6,40)
        elseif self.type == CIRCLE then
            ellipseMode(CENTER)
            ellipse(self.x,self.y,self.width,self.height)
        elseif self.type == ELLIPSE then
            ellipseMode(CENTER)
            ellipse(self.x,self.y,self.width+25,self.height)
        elseif self.type == DIAMON then
            pushMatrix()
            translate(self.x,self.y)
            rectMode(CENTER)
            rotate(45)
            rect(0,0,self.width,self.height)
            popMatrix()
        end
        -- draw text
        textMode(CENTER)
        font(self.font)
        fontSize(self.fontSize)
        fill(self.textColor)
        text(self.label,self.x,self.y)
        if self.mode == EDIT then
            pushStyle()
                strokeWidth(5)
                stroke(127, 127, 127, 255)
                fill(127,127,127, 255)
                rect(self.x-self.width/2,self.y,10,10)
                rect(self.x+self.width/2,self.y,10,10)
                rect(self.x,self.y-self.height/2,10,10)
                rect(self.x,self.y+self.height/2,10,10)
            popStyle()
        end
    popStyle()
end

function Shape:getPoint(pt)
    local x,y
    if pt == 1 then
        x,y = self.x-self.width/2,self.y
    elseif pt == 2 then
        x,y = self.x+self.width/2,self.y
    elseif pt == 3 then
        x,y = self.x,self.y-self.height/2
    elseif pt == 4 then
        x,y = self.x,self.y+self.height/2
    end
    return vec2(x,y)
end

function Shape:touched(touch)
    local v = vec2(touch.x,touch.y)
    if touch.x >= self.x - self.width/2 and touch.x <= self.x + self.width/2 and
        touch.y >= self.y - self.height/2 and touch.y <= self.y + self.height/2 then
        --click for edit mode
        if touch.state == BEGAN then
            if self.mode == NORMAL then
                self.mode = EDIT
            end 
        end
        --moving 
        if v:dist(vec2(self.x,self.y))<= 20 and self.mode == EDIT 
            and touch.state == MOVING then
                self.x = touch.x
                self.y = touch.y
                _touchHandle = true
        end
        --adjusting size
        if v:dist(vec2(self.x+self.width/2,self.y))<= 5 and self.mode == EDIT 
            and touch.state == MOVING then
                self.width = self.width+touch.deltaX
        end
        if _mode == NEW_LINE and _startobj ~= self then
            Connector(_startobj,_startpoint,self,3)
            _mode = nil
            _startobj = nil
            _startpoint = nil
        end 
    else
        if self.mode == EDIT and touch.state == MOVING then
            local temppoint 
            if v:dist(vec2(self.x+self.width/2,self.y))<= 10 then
                temppoint = 2
            elseif v:dist(vec2(self.x-self.width/2,self.y))<= 10 then
                temppoint = 1
            elseif v:dist(vec2(self.x,self.y+self.height/2))<= 10 then
                temppoint = 4
            elseif v:dist(vec2(self.x,self.y-self.height/2))<= 10 then
                temppoint = 3
            end
            if _mode == nil and temppoint then
                _mode = NEW_LINE
                _startobj = self
                _startpoint = temppoint
            end
        end
        --click outside the object to return to normal mode
        if self.mode == EDIT then
            self.mode = NORMAL
        end
    end
end
         
--Connector part 1/2

LINE = 0
ARROW_NONE = 1
ARROW_START = 2
ARROW_END = 3
ARROW_BOTH = 4
LINE_STRAIGHT = 1
LINE_CURVE = 2

Connector = class()

function Connector:init(s1,p1,s2,p2)
    -- you can accept and set parameters here
    self.shape1 = s1
    self.point1 = p1
    self.shape2 = s2
    self.point2 = p2
    self.type = LINE
    self.lineType = LINE_CURVE
    self.color = color(0, 0, 255, 255)
    self.strokeWidth = 5
    self.arrowType = ARROW_END
    self.points = {}
    self.mode = NORMAL
    World():add(self)
end

function Connector:draw()
    -- Codea does not automatically call this method
    local shape1 = self.shape1
    local shape2 = self.shape2
    local point1 = self.point1
    local point2 = self.point2
    local x1,y1,x2,y2
    x1,y1 = self:getPoint(shape1,point1)
    local p1 = vec2(x1,y1)
    local lowdist = 0
    for i = 1,4 do
        x2,y2 = self:getPoint(shape2,i)
        dist = p1:dist(vec2(x2,y2))
        if  dist<lowdist or lowdist==0 then
            lowdist = dist
            point2 = i
        end
    end
    x2,y2 = self:getPoint(shape2,point2)
    local p2 = vec2(x2,y2)
    local lowdist = 0
    for i = 1,4 do
        x1,y1 = self:getPoint(shape1,i)
        dist = p2:dist(vec2(x1,y1))
        if  dist<lowdist or lowdist==0 then
            lowdist = dist
            point1 = i
        end
    end
    x1,y1 = self:getPoint(shape1,point1)
    pushStyle()
        strokeWidth(self.strokeWidth)
        stroke(self.color)
        --Calculate points
        if (point1 == 1 or point1 == 2) and (point2 == 1 or point2 == 2) then
            local xm = math.floor((x2-x1)/2)
            self.points = {}
            if math.abs(y1-y2) <5 then
                self.points[1] = vec2(x1,y1)
                self.points[2] = vec2(x2,y2)
            else
                self.points[1] = vec2(x1,y1)
                self.points[2] = vec2(x1+xm,y1)
                self.points[3] = vec2(x1+xm,y2) 
                self.points[4] = vec2(x2,y2)
            end
        elseif (point1 == 3 or point1 == 4) and (point2 == 3 or point2 == 4) then
            local ym = math.floor((y2-y1)/2)
            self.points = {}
            if    math.abs(x1-x2)< 5 then
                self.points[1] = vec2(x1,y1)
                self.points[2] = vec2(x2,y2)
            else
                self.points[1] = vec2(x1,y1)
                self.points[2] = vec2(x1,y1+ym)
                self.points[3] = vec2(x2,y1+ym) 
                self.points[4] = vec2(x2,y2)
            end
        elseif (point1 == 1 or point1 == 2) and (point2 == 3 or point2 == 4) then
            self.points = {}
            self.points[1] = vec2(x1,y1)
            self.points[2] = vec2(x2,y1)
            self.points[3] = vec2(x2,y2) 
        elseif (point1 == 3 or point1 == 4) and (point2 == 1 or point2 == 2) then
            self.points = {}
            self.points[1] = vec2(x1,y1)
            self.points[2] = vec2(x1,y2)
            self.points[3] = vec2(x2,y2) 
        end
        --draw line        
        if self.lineType == LINE_STRAIGHT then
            for j = 1,#self.points-1 do
                line(self.points[j].x,self.points[j].y,self.points[j+1].x,self.points[j+1].y)
            end
        else
            self:Spline()
        end
        --draw arrow
        if (self.arrowType == ARROW_START or self.arrowType == ARROW_BOTH)
            and point1 == 1 then
            line(x1,y1,x1-20,y1+10)
            line(x1,y1,x1-20,y1-10)
        end
        if (self.arrowType == ARROW_START or self.arrowType == ARROW_BOTH)
            and point1 == 2 then
            line(x1,y1,x1+20,y1+10)
            line(x1,y1,x1+20,y1-10)
        end
        if (self.arrowType == ARROW_START or self.arrowType == ARROW_BOTH)
            and point1 == 3 then
            line(x1-10,y1-20,x1,y1)
            line(x1+10,y1-20,x1,y1)
        end
        if (self.arrowType == ARROW_START or self.arrowType == ARROW_BOTH)
            and point1 == 4 then
            line(x1-10,y1+20,x1,y1)
            line(x1+10,y1+20,x1,y1)
        end
        if (self.arrowType == ARROW_END or self.arrowType == ARROW_BOTH)
            and point2 == 1 then
            line(x2-20,y2-10,x2,y2)
            line(x2-20,y2+10,x2,y2)
        end
        if (self.arrowType == ARROW_END or self.arrowType == ARROW_BOTH)
            and point2 == 2 then
            line(x2,y2,x2+20,y2+10)
            line(x2,y2,x2+20,y2-10)
        end
        if (self.arrowType == ARROW_END or self.arrowType == ARROW_BOTH)
            and point2 == 3 then
            line(x2-10,y2-20,x2,y2)
            line(x2+10,y2-20,x2,y2)
        end
        if (self.arrowType == ARROW_END or self.arrowType == ARROW_BOTH)
            and point2 == 4 then
            line(x2-10,y2+20,x2,y2)
            line(x2+10,y2+20,x2,y2)
        end
        if self.mode == EDIT then
            pushStyle()
                strokeWidth(5)
                stroke(127, 127, 127, 255)
                fill(127,127,127, 255)
                rectMode(CENTER)
                rect(self.points[1].x,self.points[1].y,10,10)
                rect(self.points[#self.points].x,self.points[#self.points].y,10,10)
            popStyle()
        end
    popStyle()
end
--Connector part 2/2

function Connector:getPoint(s,n)
    local x,y 
    if n == 1 then
        x = s.x-s.width/2
        y = s.y
    elseif n == 2 then
        x = s.x+s.width/2
        y = s.y
    elseif n == 3 then
        x = s.x
        y = s.y-s.width/2
    elseif n == 4 then
        x = s.x
        y = s.y+s.width/2
    end
    return x,y
end

function Connector:touched(touch)
    -- Codea does not automatically call this method
    local points = self.points
    if touch.state == BEGAN then
        if #points == 2 then
            if (touch.x >= points[1].x-5 and touch.x <= points[2].x+5 and
               touch.y >= points[1].y-5 and touch.y <= points[2].y+5) then
                self.mode = EDIT
            elseif self.mode == EDIT then
                self.mode = NORMAL
            end
        elseif #points == 3 then
            if (touch.x >= points[1].x-5 and touch.x <= points[2].x+5 and
               touch.y >= points[1].y-5 and touch.y <= points[2].y+5) or
               (touch.x >= points[2].x-5 and touch.x <= points[3].x+5 and
               touch.y >= points[3].y-5 and touch.y <= points[3].y+5) then
                self.mode = EDIT
            elseif self.mode == EDIT then
                self.mode = NORMAL
            end
        elseif #points == 4 then
            if (touch.x >= points[1].x-5 and touch.x <= points[2].x+5 and
               touch.y >= points[1].y-5 and touch.y <= points[2].y+5) or
               (touch.x >= points[2].x-5 and touch.x <= points[3].x+5 and     
               touch.y >= points[2].y-5 and touch.y <= points[3].y+5) or
               (touch.x >= points[3].x-5 and touch.x <= points[4].x+5 and
               touch.y >= points[3].y-5 and touch.y <= points[4].y+5) then
                self.mode = EDIT
            elseif self.mode == EDIT then
                self.mode = NORMAL
            end
        end
    end
end

function Connector:SplineAt(t)
    local invT = 1-t
    local p1,p2,p3,p4
    if #self.points==2 then
        p1 = self.points[1]
        p2 = self.points[1]
        p3 = self.points[2]
        p4 = self.points[2]
    elseif #self.points==3 then
        p1 = self.points[1]
        p2 = self.points[2]
        p3 = self.points[2]
        p4 = self.points[3]
    else
        p1 = self.points[1]
        p2 = self.points[2]
        p3 = self.points[3]
        p4 = self.points[4]
    end
    return (invT^3)*p1 +
           3*(invT^2)*t*p2 +
           3*invT*(t^2)*p3 +
           (t^3)*p4
end

function Connector:Spline()
    local n = 10
    
    local from = self:SplineAt(0)
    
    stroke(self.color)
    strokeWidth(self.strokeWidth-2)
    lineCapMode(SQUARE)
    noSmooth()

    for i = 1,n do
        local to = self:SplineAt(i/n)
        
        line(from.x, from.y, to.x, to.y)
        
        from = to
    end
end


Thanks! That’s awsome!

Incredible work, @sanit!

Jeez, found this some minutes after uploading a new version of the loveCodea wrapper.

The version of the wrapper that’s available at the moment runs the program if you remove the lines “clearOutput()”, “stroke(stroke())” and “fill(fill())” (who expected such a thing? I didn’t.).

Nice work, really!

@Codeslinger Thanks. I just copied the loveCodea wrapper and ran it with my diagram. It works the same as it does on iPad. Nice jobs.