ArticlesWebDevelopmentApplications&ToolsApplications
Brotplot2.0:MakingaNewProgram
Bystupidgenius,22Sep2012
Theapplicationcanbeaccessedhere
screenshotgallery
Introduction
In2002,IcreatedmyfirstMandelbrotplotterafterreadingthisfootnoteinJamesGleick'sChaos:Makinga
NewScience(VikingPenguinInc,1987).
AMandelbrotsetprogramneedsjustafewessentialpieces.The
mainengineisaloopofinstructionsthattakesitsstartingcomplex
numberandappliesthearithmeticalruletoit.FortheMandelbrotset,
theruleisthis:ZZ
2
+C,whereZbeginsatzeroandCisthecomplex
numbercorrespondingtothepointbeingtested.[...]Tobreakoutof
thisloop,theprogramneedstowatchtherunningtotal.Ifthetotal
headsofftoinfinity,movingfartherandfartherfromthecenterofthe
plane,theoriginalpointdoesnotbelongtotheset,andiftherunning
totalbecomesgreaterthan2orsmallerthan2ineitheritsrealor
imaginarypart,itissurelyheadingofftoinfinitytheprogramcan
moveon.Butiftheprogramrepeatsthecalculationmanytimes
withoutbecominggreaterthan2,thenthepointispartoftheset.[...]
Theprogrammustrepeatthisprocessforeachofthousandsofpoints
onagrid,withascalethatcanbeadjustedforgreatermagnification.
Andtheprogrammustdisplayitsresult.(pg.231)
ThisseemedsimpleenoughthatIthoughtIcouldwritesuchaprogramIwasexcited.Idecidedtowritea
plotterusingC++andanhourlater,Iwaslookingatfractals.Fastforward10yearsandhereIam,atit
again.I'dbeenlookingforaprojectthatIcouldusetolearnHTML5andjQueryand,afterreadingalittle
bitabouthowthecanvastagworks,IrealizedthatanewMandelbrotplotterwouldservethatpurpose
nicely.
Inthisarticle,Iaimtocoverthedetailsofthatprogram.Idon'tplantoexplainthebasicsofHTML5or
jQueryUIandIcertainlydon'tplantocoveradvancedcomplexanalysisordynamics.Thatsaid,such
expertiseisnotrequiredinordertounderstandthedesignandfunctionofthisprogram.You'llsimplyneed
tohavesomeknowledgeofHTML5,JavaScript,andsomecollegelevelalgebra.
4.00(1vote)
Math:notsocomplex
TheMandelbrotsetisacomplexsetthathasafractalboundary,imagesofwhicharefamousfortheir
beautyandinfinitedetail.Acomplexsetisasetcomposedofcomplexnumbers.Acomplexnumberisany
numberoftheforma+biwhereaandbarerealnumbersandiistheimaginaryunitdefinedasi=1.
Subsequently,aiscalledtherealcomponentandbitheimaginarycomponent.Complexvariablesare
oftenwritteninboldfaced,capitalletters(e.g.Z,C)todistinguishthemfromnormalvariables.Theiterated
functionZZ
2
+CisrepeatedlyappliedtoeachcomplexpointCasdescribedintheabovefootnote.If
eithertherealorimaginarycomponentoftheresultingcomplexnumberisevergreaterthan2,thenwe
knowthattheoriginalpointCisnotpartoftheset.Inordertographtheset,therealcomponentsofthese
complexnumbersareplottedalongthexaxisandtheimaginaryalongtheyaxis.Toperformthe
calculations,thefunctionmustbesplitintoitsrealandimaginarycomponentsasfollows:
ZZ
2
+C
(a+bi)(a+bi)
2
+(a
0
+b
0
i)
(a+bi)(a+bi)(a+bi)+(a
0
+b
0
i)
(a+bi)(a
2
+2abi+bi
2
)+(a
0
+b
0
i)
(a+bi)a
2
+2abib
2
+a
0
+b
0
i
(a+bi)(a
2
b
2
+a
0
)+(2ab+b
0
)i
a
n
=a
n1
2
b
n1
2
+a
0
b
n
=2a
n1
b
n1
+b
0
Theresultingrecurrenceequationsfora
n
andb
n
giveexplicitfunctionsforcalculatingthen'thiterationfor
apointbasedonthepreviousvalueforthepointandthepointitself.
Plotter:frommathtocode
Theprimarycomponentoftheprogramistheplottereverythingelseexistsonlytosupportitandits
function.Theheartoftheplotteristhecanvaselement.Thecanvasisfirstsizedsoastoallowroomforall
theelementsoftheUIandthenforcedtoa4:3ratio.(note:thereisnoreasonforthisotherthanthe
author'spreference)
width=$(document).innerWidth()$("#ERightPanel").width()50
height=$(document).innerHeight()$("#EMainDisplay").position().top157 //top+gallery
=157
if(width*(3/4)<height)
height=Math.round(width*(3/4))
else
width=Math.round(height*(4/3))
xCenter=Math.round(width/2)
yCenter=Math.round(height/2)
iPix=height*width
Inadditiontothecanvas,theplotteralsousesthreeJavaScriptarrays:twovaluearraysaiXandaiYfor
therealandimaginarycomponentsandanactiveflagarray.Allofthearraysaresizedthesameasthe
canvas,sothereisaonetoonecorrespondencebetweenindexesofthevariousarrays.Allpointsbegin
witha0valueinbothvaluearraysandareflaggedastrueintheactivearray.
for(vari=0i<iPix++i)
{
afActive[i]=true
aiX[i]=0
aiY[i]=0
surface.data[i*4]=0
surface.data[i*4+1]=0
surface.data[i*4+2]=0
surface.data[i*4+3]=255
}
Therealandimaginarycomponentsofthevalueforeachpointarecomputedseparatelyaccordingtothe
functionsdescribedaboveandstoredintheirrespectivevaluearrays.Sincethecomponentfunctionsrely
onthecomponentvaluesoftheoriginalpoint,itisnotpossibletosimplytraversethearraysinalinear
fashionthe(x,y)coordinatesofthecurrentpoint(a
0
,b
0
)mustbeknown.Tocalculatetheneeded
components,somesimpleresiduearithmeticusingthecurrentarrayindex,i,isused.
a
0
=i%width
b
0
=Math.Floor(i/width)
TranslationoftheplotisachievedbyalteringtheoriginalpointCbyaddinganoffsettermlikethis:
a
0
=(i%widthxOff)
b
0
=(Math.Floor(i/width)yOff)
Magnificationoftheplotisachievedinasimilarmannerbymultiplyingbyascalingfactorlikethis(where
fMag1):
a
0
=(i%widthxOff)*fMag
b
0
=(Math.Floor(i/width)yOff)*fMag
Foreachindextraversedinthearray,theplotmovesanfMag'thofapointoffsetfromtheorigin.Thefinal
computationoftheplot,incorporatingtheaboveintotherecurrenceequations,lookslikethis:
varx=aiX[i]
vary=aiY[i]
aiX[i]=x*xy*y+(i%widthxOff)*fMag
aiY[i]=2*x*y+(Math.Floor(i/width)yOff)*fMag
Armedwiththiscomputation,theplottercantraversethevaluearrays,applyingthearithmeticalruleto
eachpointasdescribedinthefootnote.Afterinitializingthedisplay,aJavaScriptintervalissettocallthe
frame()functionandupdatethedisplay.
t=setInterval(frame,1)
functionframe()
{
if(!colorMan.bActive)
return
varp
if(beginColorCycle)
colorMan.inc(colorFunc)
varr=colorMan.comp(0)
varg=colorMan.comp(1)
varb=colorMan.comp(2)
for(vari=0i<iPix++i)
{
if(!afActive[i])
continue
varx=aiX[i]
vary=aiY[i]
aiX[i]=x*xy*y+(((i%width)xOff)*fMag)
aiY[i]=2*x*y+((Math.floor(i/width)yOff)*fMag)
if(aiX[i]>=2||aiY[i]>=2)
{
afActive[i]=false
beginColorCycle=true
continue
}
if(!beginColorCycle)
continue
p=i*4
surface.data[p]=r
surface.data[p+1]=g
surface.data[p+2]=b
}
context.putImageData(surface,0,0)
}
Ifthevalueofeithercomponentisgreaterthanorequalto2,thepointisflaggedasfalseintheactive
array.Theplottertakesanearlyoutapproach,skippingthepointimmediatelyifitisnotactive.If,afterits
valueisupdated,thepointisstillactive,itscoloristhenincremented.Theplotterdoesthisonceforeach
pointintheplotandthenrepeatstheprocess.
ColorizingofthepixelsishandledbyanobjectdelBrot,namedsuchsinceitcontrolsthecolorgradientof
theplot.Thisapproachallowsagreatdealofflexibilityinhandlingmultiplecolorizingschemessincethe
colordataismaintainedinternally.Becauseitisthecolormanager,theobjectinstanceisnamedcolorMan
and,whencombinedwiththeobjectname,thisofcourseresultsinacolorMandelBrot.
UI:controllingthechaos
TheuserinterfaceofthisprogramreliesheavilyuponjQueryUI.AllofthejQueryUIwidgetsusedinthe
programarevirtuallyunalteredversionsofthedemosatjqueryui.com[link].IntypicaljQueryfashion,they
areallinitializedinthe$(document).ready()handler.
Theprimarycomponentisananimatedaccordion[link].Itspurposeistohideinformationuntilrequested.
Thespecificdirectionspertainingtothevarioussectionsoftheprogramarehiddenuntilthatsectionis
activated.Thispreventstheuserfrombeingoverwhelmedbyascreenfulloftext.Thisalsoallowsforvery
thoroughinstructionstobewrittenwithoutworryastothetotalamountoftext.Whentheaccordion'sstate
changes,thehandlercheckswhichsectionisactiveandeithershowsorhidespiecesoftheUIasthey
gainorlosefocus.
$("#EControls").accordion
({
//fillSpace:true,
autoHeight:false,
change:function(event,ui)
{
varactive=$("#EControls").accordion("option","active")
if(active==3)
{
if(!$("#EImageGallery").is(":visible"))
$("#EImageGallery").show("drop",null,500)
}
else
if($("#EImageGallery").is(":visible"))
$("#EImageGallery").hide("drop",null,500)
if(active==1)
{
if(!$("#ERightPanelLower").is(":visible"))
{
loadCoord()
$("#ERightPanelLower").show("drop",{direction:"right"},500)
}
}
else
if($("#ERightPanelLower").is(":visible"))
$("#ERightPanelLower").hide("drop",{direction:"right"},500)
}
})
Anotherimportantcomponentistheanimatedslider.Thiscomponentisusedforthesavedcoordinate
pickeraswellastheimagegallery.Here,theinterfaceusesmotiontoindicaterelevance(i.e."Hey,that
thing'smovingitmustberelatedtothethingIjustclicked.").ThisisimportantbecauseseveraloftheUI
elementsareplacedoutsidethemainaccordioncomponenttoreduceclutterandkeeptheaccordions
sizeconsistent.Thismeansthatrelatedelementsaredisplayedindifferentareasofthescreen,andso
thereisariskoftheusernotrealizingthattheyaremeanttobeusedtogether.Animatingtheseparated
componentswhentheybecomerelevantshouldhelptheusermakethatassociation.
Theplotcanbecontrolledviathemousebyfollowingtheinstructionsinthefirstsectionoftheaccordion.
ThePlotcontrolssectioncontainsadditionalcontrolsoverthetranslationandzoomoftheplot.These
controlsareallimplementedasadjustmentstotheplotparameters.Plottranslationisacheivedby
adjustingtheplotoffsetbythevector<centerclickedpoint>.
$("#EMainDisplay").mousedown(function(e)
{
varleft=e.pageX$("#EMainDisplay").offset().left
vartop=e.pageY$("#EMainDisplay").offset().top
switch(e.which)
{
case1:
xOff+=xCenterleft
yOff+=yCentertop
break
case3:
varf=$("#EFactor").val()
if(e.shiftKey)
f=1/f
fMag/=f
xOff=xCenter(leftxOff)*f
yOff=yCenter(topyOff)*f
break
}
initDisplay()
})
Zoomiscontrolledinasimilarmanner,bymultiplyingordividingtheplotscalingfactorbythezoomfactor.
ThedynamicrangeoptionintheRendercontrolssectionisenabledbydefaultandincreasesthevisible
detailoftheplot.Athighermagnifications,itcantakemanyiterationsbeforepixelsbegintogetflaggedout
ofset.Withoutdynamicrangeenabled,theplotwouldimmediatelybegintoincrementthecolorvaluesof
pixels,wastingtheavailablecolorsonanessentiallyblankimage.Dynamicrangesupressescolorization
untilthefirstpixelisflaggedoutofset.
$("#EDRange").is(":checked")?beginColorCycle=false:beginColorCycle=true
functionframe()
{
...
if(aiX[i]>=2||aiY[i]>=2)
{
afActive[i]=false
beginColorCycle=true
continue
}
if(!beginColorCycle)
continue
...
}
AllofthecolorizationschemesarehandledbydelBrot.Thedefaultcolorizationmodeincrementsasingle
colorcomponentfrom0255beforemovingtothenextcolor,startingwithred,thengreen,andthenblue.
Onceallthecolorcomponentsareatmaximumvalue(purewhite),delBrotdisablesitself.
function(mode)
{
switch(mode)
{
case0://default,singlepass
case1://colorcycling
if(!this.bActive)
return
if(aiComponents[0]<255)
aiComponents[0]++
else
{
if(aiComponents[1]<255)
aiComponents[1]++
else
{
if(aiComponents[2]<255)
aiComponents[2]++
else
{
if(mode==0)
this.bActive=false
else
{
aiComponents[0]=0
aiComponents[1]=0
aiComponents[2]=0
}
}
}
}
break
case2://coloroscillation
if(!this.bActive)
return
if(bForward)
if(aiComponents[0]<255)
aiComponents[0]++
else
{
if(aiComponents[1]<255)
aiComponents[1]++
else
{
if(aiComponents[2]<255)
aiComponents[2]++
else
{
bForward=false
}
}
}
else
if(aiComponents[2]>0)
aiComponents[2]
else
{
if(aiComponents[1]>0)
aiComponents[1]
else
{
if(aiComponents[0]>0)
aiComponents[0]
else
bForward=true
}
}
break
}
}
Thecolorcyclingmodefunctionsthesameasthedefaultmodeexceptthatitresetsthecurrentcolorto
blackuponreachingtheend.Coloroscillationincrementstheindividualcolorchannelsinthesame
mannerastheothermodesexceptthatonceit'sreachedtheenditbeginstodecrementthecolor,causing
theactivepixelstooscillatebetweenblackandwhite.
HTML5'slocalStorageobjectallowsforplotsettingsaswellasimagestobesavedlocally.Twomaster
entriesareusedplotsandshotstostorethenamesoftheentriescontainingtheactualplotandimage
data.Sincewebstorageonlyworkswithtext,imagedataisfirstconvertedtobase64encodedtextusing
thecanvas'stoDataURL()method.Unfortunately,webstorageistypicallyverylimitedanduncompressed
imagedataconsumesalotofspace,solocalstorageofimagedataisusuallylimitedto36pictures.
Closing
Ihavebeenverypleasedwiththewaythisprojectturnedout.MyintentionwastolearnHTML5and
jQuery,andthisprojectallowedmetodojustthat.Ipersonallyfinditextrarewardingthat,inthisversion,I
wasabletoimplementsomefeaturesthatInevergotaroundtoimplementinginmypreviousversion,such
asimprovedcolorcontrolsandtheabilitytotakescreenshots.Ofcourse,aswiththepreviousversion,this
timeIamagainleavingbehindsomeunfinishedideas(tobeimplementedperhapsinanother10yearsor
so).Thereare,forexample,ahandfulofbugsofwhichI'mawareIhavesomeideasforuserdefined
colorschemesandI'dreallyliketoaddsomeparallelismusingwebworkers.Asitis,evenwithoutparallel
processing,thisversionisnotmuchslowerthanmyoldC++version(andtheenhancedimagequalityand
UIfeaturesmorethanmakeupforthedifference).IfIwriteanotherversion,thenextonewilllikelybein
assembly.I'dbecurioustodoasidebysidespeedtestand,also,I'dliketowriteaversionthatallowsfor
arbitrarilysmallrealnumbers(limitedbycomputermemory,ofcourse).Thecurrentversioncanreacha
magnificationfactorofonly10
16
duetolossofprecision,soI'dliketocreateaversioninassemblythat
canhandleeventiniernumberstoseehowfardowntherabbitholereallygoes.Forthetimebeing,
however,IthinkI'mgoingtocallthisonedoneagain.
Thanksforreading,andIhopeyouenjoyusingtheprogramasmuchasIenjoyedmakingit!
stupidgenius
SoftwareDeveloper(Junior)Yieldex
UnitedStates
NoBiographyprovided
License
Thisarticle,alongwithanyassociatedsourcecodeandfiles,islicensedunderTheGNUGeneralPublic
License(GPLv3)
AbouttheAuthor
Permalink|Advertise|Privacy|Mobile
Web01|2.8.140509.1|LastUpdated23Sep2012
ArticleCopyright2012bystupidgenius
EverythingelseCopyrightCodeProject,19992014
TermsofUse
CommentsandDiscussions
5messageshavebeenpostedforthisarticleVisit
http://www.codeproject.com/Articles/462862/BrotplotMakingaNewProgramtopostandview
commentsonthisarticle,orclickheretogetaprintviewwithmessages.