Code archives/Graphics/Line Graph

This code has been declared by its author to be Public Domain code.

Download source code

Line Graph by Pineapple2014
The func object is the thing you want to use to get the data. The graph object is the thing you want to use for UI. The graph object assumes that all vector values in the func object will be 0 to 1 inclusive.

The implementation is a little messy but hopefully the example will make usage clear.

Here's what it looks like in action:


Left click selects a vector, holding down and dragging moves it. Left-clicking in empty space creates a new vector. Right-click deletes a moused-over vector. Holding shift and dragging the left mouse selects a group of vectors which can then be moved together. Holding shift and dragging the right mouse deletes a group of vectors.

Dependencies:
vect2d.bmx for vector type: http://blitzbasic.com/codearcs/codearcs.php?code=2960
interpolate.bmx for interpolation things: http://blitzbasic.com/codearcs/codearcs.php?code=2955
mouse.bmx for mouse input: http://blitzbasic.com/codearcs/codearcs.php?code=3072
' 	--+-----------------------------------------------------------------------------------------+--
'	  |   This code was originally written by Sophie Kirschner (sophiek@pineapplemachine.com)   |  
' 	  | It is released as public domain. Please don't interpret that as liberty to claim credit |  
' 	  |   that isn't yours, or to sell this code when it could otherwise be obtained for free   |  
'	  |                because that would be a really shitty thing of you to do.                |
' 	--+-----------------------------------------------------------------------------------------+--


SuperStrict

'Framework brl.glmax2d ' only needed for example program
Import brl.max2d
Import brl.polledinput
Import "vect2d.bmx"		' Code archive: http://blitzbasic.com/codearcs/codearcs.php?code=2960
Import "interpolate.bmx"	' Code archive: http://blitzbasic.com/codearcs/codearcs.php?code=2955
Import "mouse.bmx"		' Code archive: http://blitzbasic.com/codearcs/codearcs.php?code=3072


' Example Program

Rem

' Set up the window
AppTitle="Graph thing"
Const gw%=512,gh%=256
Graphics gw,gh
SetClsColor 32,32,32

' Create the graph object
Local g:graph=New graph

' Main loop
Repeat
	Cls
	
	' Draw the stuff
	Const margin%=32
	SetColor 0,0,0
	DrawRect margin,margin,gw-margin-margin,gh-margin-margin
	SetColor 255,255,255
	g.render margin,margin,gw-margin-margin,gh-margin-margin
	
	' Information about the selected vector
	Local o:vect2d=g.mouseovervect()
	Local vt$="Vector: ( Mouse over for info )"
	If o Then vt="Vector: ( "+o.x+" , "+o.y+" )"
	DrawText "Value at mouse X: "+g.getvalue(Double(mx-margin)/(gw-margin-margin)),2,gh-30
	DrawText vt,2,gh-16
	
	' Update the stuff
	g.update margin,margin,gw-margin-margin,gh-margin-margin
	updatemouse
	
	Flip
	If AppTerminate() End
Forever

EndRem



' Has a list of points and an interpolation function.
Type func
	' Interpolation function
	Field interpolate!(a!,b!,x!)=interpolate_linear
	' List of vect2d objects
	Field vects:TList=CreateList()
	' Get the Y value for some X
	' Returns 0 if there are no vectors in the list. Returns the value of the nearest vector if the X is outside the func's range.
	Method getvalue!(x!)
		If vects.isempty() Return 0
		Local pvx!=-1,pvy!=0
		For Local v:vect2d=EachIn vects
			If v.x>x Then
				If pvx=-1 Then
					Return v.y
				Else
					Return interpolate(pvy,v.y,(x-pvx)/(v.x-pvx))
				EndIf
			EndIf
			pvx=v.x;pvy=v.y
		Next
		Return pvy
	End Method
End Type

' A UI thing for the func object. Its func object will always keep its vectors within 0-1 inclusive on both axes.
Type graph
	' The func object
	Field f:func=New func
	' Size of squares to draw a vector and the size of the outline for if it's been selected
	Field vectsize%=4,selvectsize%=10
	' A bunch of random crap for handling selection of one or more vectors
	Field sel:TLink,seldist%=14
	Field closest:vect2d=Null,cd!=0,clink:TLink
	Field sellist:TList,shiftmode%=shiftnull,shiftstart:vect2d=New vect2d,shiftend:vect2d=New vect2d,selectinglist%=0
	Const shiftnull%=0,shiftselect%=1,shiftdelete%=2
	Field unselatmouseup%=0
	' Renders the graph to the specified rectangle
	' Setting interp to true uses the func object's interpolation function, setting to false uses linear. (Assuming linear is a lot faster)
	Method render(x%,y%,w%,h%,interp%=False)
		Local pvx!=-1,pvy!=-1
		For Local v:vect2d=EachIn f.vects
			If pvx>=0 Then renderline pvx,pvy,v.x,v.y,x,y,w,h,interp
			Local vx!=v.x*w+x,vy!=v.y*h+y
			DrawRect vx-vectsize/2,vy-vectsize/2,vectsize,vectsize
			pvx=v.x;pvy=v.y
		Next
		If sellist
			For Local link:TLink=EachIn sellist
				Local v:vect2d=vect2d(link._value)
				Local vx!=v.x*w+x,vy!=v.y*h+y
				drawrectoutline vx-selvectsize/2,vy-selvectsize/2,selvectsize,selvectsize
			Next
		ElseIf sel Or (closest And cd<seldist) Then
			Local v:vect2d=closest
			If sel Then v=vect2d(sel._value)
			Local vx!=v.x*w+x,vy!=v.y*h+y
			drawrectoutline vx-selvectsize/2,vy-selvectsize/2,selvectsize,selvectsize
		EndIf
		If shiftmode<>shiftnull Then
			Local vx1!=shiftstart.x*w+x,vy1!=shiftstart.y*h+y
			Local vx2!=shiftend.x*w+x,vy2!=shiftend.y*h+y
			drawrectoutline Min(vx1,vx2),Min(vy1,vy2),Abs(vx1-vx2),Abs(vy1-vy2)
		EndIf
	End Method
	' Draws a line for the graph. Setting interp to true uses the func object's interpolation function, setting to false uses linear. (Assuming linear is a lot faster)
	' (You shouldn't need to do anything with this)
	Method renderline(x1!,y1!,x2!,y2!,x%,y%,w%,h%,interp%=False)
		Local px1!=x1*w+x,py1!=y1*h+y
		Local px2!=x2*w+x,py2!=y2*h+y
		If Not interp
			DrawLine px1,py1,px2,py2
		Else
			Local py%=0
			If px1=px2 Then
				DrawLine px1,py1,px2,py2
			Else
				For Local lx%=px1 Until px2
					Local ix!=Double(lx-x)/w
					Local iy!=f.getvalue(ix)
					Local ny%=iy*h+y
					If lx>px1 Then DrawLine lx-1,py,lx,ny
					py=ny
				Next
			EndIf
		EndIf
	End Method
	' Returns the vector the mouse is over, if any
	Method mouseovervect:vect2d()
		If sel Then Return vect2d(sel._value)
		If closest And cd<seldist Then
			If sellist
				For Local link:TLink=EachIn sellist
					If vect2d(link._value)=closest Then Return closest
				Next
				Return Null
			Else
				Return closest
			EndIf
		EndIf
		Return Null
	End Method
	' Get the value at a specified x (0-1 inclusive)
	Method getvalue!(x!)
		Return f.getvalue(x)
	End Method
	' Updates the graph object, allowing for mouse input
	Method update(x%,y%,w%,h%)
		Local px!=Double(mx-x)/w,py!=Double(my-y)/h
		Local mi%=mousein(x,y,w,h)
		Local shift%=shiftdown()
		' Clickkity
		If unselatmouseup And (Not lmbd) Then sellist=Null;sel=Null;unselatmouseup=0
		If mi Then
			closest=Null;cd=0;clink=Null
			Local link:TLink=f.vects._head._succ
			While link<>f.vects._head
				Local v:vect2d=vect2d(link._value)
				Local vx!=v.x*w+x,vy!=v.y*h+y
				Local d!=Max(Abs(mx-vx),Abs(my-vy))
				If closest=Null Or d<=cd Then
					closest=v;cd=d;clink=link
				EndIf
				link=link._succ
			Wend
			If shift
				If lmb Or rmb Then
					sellist=Null
					If rmb Then shiftmode=shiftdelete Else shiftmode=shiftselect
					shiftstart.x=px;shiftstart.y=py
					shiftend.x=px;shiftend.y=py
				EndIf
			Else
				If closest And cd<seldist Then
					If lmb Then
						sel=clink
						If sellist And sellist.contains(clink) Then
							selectinglist=1
						Else
							sellist=Null
						EndIf
					ElseIf rmb And clink._pred<>f.vects._head And clink._succ<>f.vects._head
						clink.remove
						sellist=Null
						sel=Null
					EndIf
				ElseIf lmb
					If Not sellist Then
						mouseaddvect(x,y,w,h)
					Else
						sellist=Null
					EndIf
				ElseIf rmb
					sellist=Null
				EndIf
			EndIf
		Else
			closest=Null
		EndIf
		' Shiffity
		If shiftmode<>shiftnull Then
			If Not shift Then
				shiftmode=shiftnull
			Else
				shiftend.x=Min(1,Max(0,px));shiftend.y=Min(1,Max(0,py))
				If (shiftmode=shiftselect And Not lmbd) Or (shiftmode=shiftdelete And Not rmbd) Then
					Local list:TList=selectbox(Min(shiftstart.x,shiftend.x),Min(shiftstart.y,shiftend.y),Max(shiftstart.x,shiftend.x),Max(shiftstart.y,shiftend.y))
					If shiftmode=shiftselect
						sellist=list
					Else
						For Local link:TLink=EachIn list
							If link._pred<>f.vects._head And link._succ<>f.vects._head Then link.remove
						Next
					EndIf
					shiftmode=shiftnull
				EndIf
			EndIf
		EndIf
		' Drag
		If lmbd And sel Then
			If sellist
				Local selv:vect2d=vect2d(sel._value)
				Local dx!=px-selv.x,dy!=py-selv.y
				For Local link:TLink=EachIn sellist
					Local v:vect2d=vect2d(link._value)
					If link._pred=f.vects._head Or link._succ=f.vects._head Then
						dx=0
					Else
						If dx<0
							If Not sellist.contains(link._pred)
								Local pv:vect2d=vect2d(link._pred._value)
								dx=-Min(-dx,v.x-pv.x)
							EndIf
						ElseIf dx>0
							If Not sellist.contains(link._succ)
								Local sv:vect2d=vect2d(link._succ._value)
								dx=Min(dx,sv.x-v.x)
							EndIf
						EndIf
					EndIf
					If dy<0
						dy=-Min(-dy,v.y)
					ElseIf dy>0
						dy=Min(dy,1-v.y)
					EndIf
				Next
				For Local link:TLink=EachIn sellist
					Local v:vect2d=vect2d(link._value)
					v.x:+dx;v.y:+dy
				Next
			Else
				Local v:vect2d=vect2d(sel._value)
				v.y=py
				If sel._pred<>f.vects._head And sel._succ<>f.vects._head Then
					Local pv:vect2d=vect2d(sel._pred._value)
					Local sv:vect2d=vect2d(sel._succ._value)
					v.x=Min(sv.x,Max(pv.x,px))
				EndIf
				v.x=Min(1,Max(0,v.x))
				v.y=Min(1,Max(0,v.y))
			EndIf
		Else
			sel=Null
		EndIf
	End Method
	' Gets all the vectors within a space
	Method selectbox:TList(minx!,miny!,maxx!,maxy!)
		Local list:TList=CreateList()
		Local link:TLink=f.vects._head._succ
		While link<>f.vects._head
			Local v:vect2d=vect2d(link._value)
			If v.x>=minx And v.y>=miny And v.x<=maxx And v.y<=maxy Then list.addlast link
			link=link._succ
		Wend
		Return list
	End Method
	' Add a new vector at the mouse location
	Method mouseaddvect(x%,y%,w%,h%)
		Local px!=Double(mx-x)/w,py!=Double(my-y)/h
		px=Min(1,Max(0,px))
		py=Min(1,Max(0,py))
		If f.vects.isempty() Then
			Local v0:TLink=f.vects.addlast(vect2d.Create(0,py))
			Local v1:TLink=f.vects.addlast(vect2d.Create(1,py))
			sellist=CreateList()
			sellist.addlast v0
			sellist.addlast v1
			sel=v0
			unselatmouseup=1
		ElseIf f.vects.count()<2 Then
			' this really shouldn't happen but ok
			Local v:vect2d=vect2d(f.vects.first())
			Local n:vect2d=vect2d.Create(0,py)
			If v.x>px Then
				v.x=1
				n.x=0
				f.vects.addfirst n
			Else
				v.x=0
				n.x=1
				f.vects.addlast n
			EndIf
		Else
			Local link:TLink=f.vects._head._succ
			While link<>f.vects._head
				Local v:vect2d=vect2d(link._value)
				If v.x>px Then
					Local n:vect2d=vect2d.Create(px,py)
					Local nlink:TLink=New TLink
					nlink._value=n
					nlink._succ=link
					nlink._pred=link._pred
					link._pred._succ=nlink
					link._pred=nlink
					sel=nlink
					Exit
				EndIf
				link=link._succ
			Wend
		EndIf
	End Method
	' Draws the outline of a rectangle
	Function DrawRectOutline(x%,y%,w%,h%,skipn%=0,skips%=0,skipe%=0,skipw%=0)
		If Not skipn DrawLine x,y,x+w-1,y
		If Not skipw DrawLine x+w-1,y,x+w-1,y+h-1
		If Not skips DrawLine x+w-1,y+h-1,x,y+h-1
		If Not skipe DrawLine x,y+h-1,x,y
	End Function
	' Checks for shift modifier
	Function shiftdown%()
		Return KeyDown(key_lshift) | KeyDown(key_rshift)
	End Function
End Type

Comments

dna2014
this is great.
A routine like this can be used in many audio applications and more but most of where its currently seen is in music / Audio applications.

A Great Idea.


Code Archives Forum