Get it Right in Black & White Episode 4

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello hey hey everybody give a minute for everyone to join who's here so far oh maybe i need to add them wait a minute hello sriram hi good morning kiran how are you doing good thanks how are you good uh sorry i didn't have time to finish the assignment but i'm still excited to join and continue on uh today's session where we go to work nice yeah no worries at all yeah sorry i've been slammed with like a bunch of assignments i think that'll clear up this week and some of the traffic will get unblocked everything is kind of signed up for too much work and now i'm kind of suffering because of that yeah i know that feeling hey adil hello hello hello everyone hi karen hi good morning hi how are you all good good all right all right i think we can uh get started yeah so we've got a couple people on the live stream let's see hello everyone i'll be checking the chat so feel free to drop notes drop questions all right we've got sriram adil anita today let's dig in oh i have to share my screen over there now you all can see this screen in the meeting all right welcome to episode 4 of get it right in black and white today what we're going to do is use d3 to reproduce this solar with piece in its fullness with different shapes what we'll cover includes a a review of exercise submissions from last week and then we're going to start using d3 selections and data joins we're going to take what we did last week and refactor it you know move the code around change it so that it does the same thing but uses d3 and then we'll look at d3 symbols d3 actually provides these nice shapes that we can use and along the way to reproducing that solar whit piece we're going to talk about svg group elements translating group elements meaning moving them around and we'll touch upon d3 point scales and potentially band scales depending on how far we get all right so this is the solar with piece that we're aiming to reproduce and today what i'm hoping to do is get to the point where we have multiple different shapes in this mask and if we get to that point and still have time i want to tackle the problem of the background being different for each of these i think the first pass would be you know the background is all uh vertical lines and the the filled in shapes are all horizontal lines that would be the first phase that we're going to shoot for so phase two um would be to make it so that each square here has has the the horizontal lines in the background and the vertical lines in the foreground alternating with the vertical lines in the background and the horizontal lines in the foreground so i don't know i hope it's not too ambitious let's give it a shot right so let me pull up the exercises from last time all right here's the forum post from last week let's see what we've got um felipe email he's been following and i'm very impressed by this work um this is actually a game check this out uh here it's my face with you know this solo wit esque kind of a thing going on pretty wild and it changes over time uh i'm just blown away by this very nice very nice work yeah and i think he actually made this into a full-blown game um although i can't find it right now and look he made this one earlier oh this is so good look at that incredible really incredible so here's another one from w gloss this is pretty neat there's some randomness here very cool very cool yeah vanilla javascript and dom api here's one from nita check this out whoa what that's crazy oh my gosh nita you want to talk about this a little bit yes can i see the code so i added animations on the y position of the rectangle each rectangle whoa dot animate fastest wow i've never even seen this method that's very cool wow amazing very nice very nice but how is how are they all going could you mute now how how is it that each of these are are not going all at the same time it seems like it's the duration of this animate is is a random number that's really cool really cool so that's how that effect happens very nice and whoa look at this one very nice it almost looks like three-dimensional uh balls or something wow very cool very cool let's see oh here it is he posted again this is the game version so i was i was quite impressed by this check this out you have to you have to you have this is so funny you have to click when it's black and white so that's i just got one point and if you click when it's not black and white okay i clicked on a black and white again but if you click when it's color you lose a life see that one less little icon there so this is just through the roof really great job um really creative very nice work here's another one from nita a bunch of circles wow beautiful beautiful grid of circles very nice do you want to talk about this one at all yeah i would like to talk about it so what i did here uh we are actually positioning each circle in x and y direction so we are using i and j twice so we can get the kind of matrix values in the matrix i and j for each cell there something like that you can say it is going in the x direction and y directions at the same time beautiful you are getting the whole grid gets filled up depending on how many numbers circle we are using and we can add animation and other things on top of it and i did it in another example i kept forking it again and again it was just so fun awesome awesome thank you yeah this is great so it's a nested for loop where i goes from zero to count j goes from zero to count and for each combination of i and j the circle is positioned very nice very nice it's pretty close to this other solo whip piece so that could be the next phase perhaps alright that's it that's all the work very cool all right any uh questions or discussion so far if not i think i'll dive into the coding all right here we go here's our piece that we made last time i'm going to start by forking this and i'll call it solo it reproduction with d3 all right open the editor the first order of business that i want to do here is include d3 into our app here one way to do that would be to add a script tag to the head and put the source equal to something uh usually from a cdn like unpackage a cdn is a content distribution network that essentially hosts files on the web that you can pull in however in modern web development usually you use a a node js based build system uh like using webpack or rollup or using some starter app uh system framework thing and there's a relatively new feature of this hub that lets you approximate package.json which i'm very excited to start using so let's let's do that i'm going to make a new file called package.json package.json by the way it's like a it's a standard in a way npm and the node npm stands for node package manager it's a whole ecosystem of javascript libraries and so npm expects this thing called package.json and you can put a bunch of stuff in here like set the license and you know add dependencies dependencies is what matters for us so it's you specify this object that has dependencies and then the library name and the version we can use this to pull in d3 to our program in package.json i'm going to make it an object in in json format javascript object notation format json and the key will be oh there's some something strange happening all right so we can type dependencies as the key and the object can contain d3 at some specific version and let me check which version d3 is at the way i like to check that is to go to unpackage.com which is a cdn that hosts script files and just type d3 and it will automatically fill in the latest version so we're at version 6.6.1 so in here d3 version will be 6.6.1 all right that's like a bare minimum package.json and then what we can do is introduce a a script file where our javascript can go and just by convention i'm going to call it index.js and what we can do is take all of this javascript that was in this script tag directly on the page and move it over to index.js and everything still runs now i can delete that script tag over here and we have this sort of new setup in which we can import from d3 so here we go let's pull in something from d3 i'm going to say import selection from d3 this is es6 module syntax to import from packages and this syntax you can use again with all the modern templates and whatnot and so let's see if this worked console.log selection i just want to see did it get defined as anything in the dev tools we can say that indeed it's a function array it's a function from d3 it's got these weird uh characters because it comes from a minified uh file the the d3 build but anyway this means that now we can start using d3 so let me talk about selection if you google d3 selection and click the first result it's the documentation page for this package called d3 dash selection and d3 by the way is structured as a collection of many smaller packages that are all composed into this one monolithic library called d3 and so if you see in in d3 itself it just it exports things from all of these d3 packages which you could use on their own but for convenience sake i'm just going to pull in the whole d3 build which contains all of this stuff but anyway d3 selection is one of these things and i'm going to pull in from there and feel free to stop me at any time with questions by the way yeah i haven't really introduced um d3 itself let me stop and do that since this is this first time that we're pulling in d3 i thought i would take a moment to discuss d3 and why we'd want to use it uh overall just some broad context for this d3 stands for data-driven documents it refers to documents as in the dot the html document object model d3 has utilities for dom manipulation which we did last time with the vanilla javascript api for dom manipulation which was quite verbose a lot of that stuff can be changed such that it uses d3 for dom manipulation and the code will shrink down it'll be a lot simpler to read and use in addition to dom manipulation d3 has all sorts of utilities for building data visualizations so that's why it has become sort of the de facto standard for building visualizations on the web today the whole rest of this series is going to be diving into using d3 to make all these different types of visualizations but as a starting point i wanted to introduce d3 in a way that's disconnected from data and all the complexities that comes along with data in this solar with exercise here d3 selection is the package within the set of d3 packages that does dom manipulation here are some examples of how to use it um and and i'll go you know we'll craft these ourselves but for reference this page is the canonical documentation for d3 selections all right now that we've imported selection from d3 we can start to use it to replace this code that we had from last time so here's what i'm going to do i'm going to comment out everything and then re-implement it with d3 one block at a time the first block to consider is the one that sets up the svg element i'm going to comment this out as well and replace it with some d3 based stuff select body is what we need to do to get a d3 selection of the body element and now that i think of it we really need to import select from d3 not selection so we've got select from d3 we select the body and a d3 selection instance is the sort of complicated thing that we'll be learning more about but it has this this pattern of using it called method chaining where once you invoke something and you get a d3 selection back that thing that gets returned by this function has a bunch of methods on it one of which is append and you can pass into append the tag name like svg and that returns the selection of the svg element that was appended to the body so this this one line here does the exact same thing as this line and this line it creates a new svg element and appends it to the body to set these attributes on this well first of all let me save this as a variable i'll call it svg once we've got that we can say svg.attr which is short for attribute internally this actually invokes set attribute and the signature is compatible with the vanilla dom api method set attribute so i can just paste this stuff here we set the attribute of width to be the value of the variable width and same thing for height the height attribute gets the value of the height variable now we can inspect the dom to see if it worked and sure enough there it is there's an svg element with width 960 and height 500. any questions at this point all right so this is one way to set up the svg element um due to this method chaining thing you can actually combine these together into one big statement so instead of having multiple different statements it could be one giant statement where we chain these things together so we append this the svg and then on the selection of the svg we set the width and height all right so we've got our svg set up the thing that i wanted to do next was add the the rectangles that are in the background that would be a good next thing to add yeah this block here where it's a for loop that adds a bunch of rectangles let's do this with d3 so i'll bring that up here the way we would do this in d3 is we need to have an array to work with and then once we have an array we can use the d3 data join concept so here's what we can do i'm going to use this code as a template to build up an array of objects where each object represents the attributes of a single rectangle so i'll paste that uncomment it and then i can initialize an array i'll call it marks because it represents the visual marks that are going to appear on the page i'll initialize it like this to an empty array and then in the body of this for loop instead of creating dom elements i'm just going to push objects onto this array meaning you know append them to the array add new entries to this list of things by using the push method so we can say marks.push and pass in an object literal and on this object literal i can build up all the things that it needs to know to render a rectangle so y for example could be set to i times 20 width could be set to width height could be set to 10. i'm just copying from what's there and the mask can be set to this string here that's how we can build up an array of objects that describe the rectangles this is not using d3 at all this is just pure javascript and just to see that it worked let me console.log marks sure enough we get this array and we can see it's got a bunch of objects where the only thing that really varies is y right so far so good you have a question yes we are doing this because dt requires and parade as a data right yeah yeah that's right um you know your audio is really bad i don't know what it is but maybe grab a camera or something the reason why we're setting up this array is because d3 expects an array to work with to use this data join feature of d3 which we're going to use right now i'm going to refer to this svg selection from earlier to add rectangles inside of it and to use this pattern of d3 data joints we can take a d3 selection and say you know that selection dot select all rect this makes a selection of all rectangles that are on the page already and at the point that where this code runs there are none but the fact that there are none is useful information to d3 because it knows that okay the set of dom elements on the page it doesn't contain what it needs to therefore i have to add it so once we've said select all we can say dot data marks this produces a d3 data join that has a bunch of methods on it um one method which was added fairly recently in d3 is dot join it's a simplified api to the other stuff that was in use before like uh and dot enter dot merge sort of stuff and we'll you know we'll get deeper deeper into this but this is the simplified api that we can use for our case so what it's going to do here is create one rectangle for each of these marks and then on this selection that contains all these newly minted rectangles we can set some attributes we can set the attribute of i'm looking at this object here we want to set all four of these attributes we can set the y attribute to be well what we want to set it to is the y value from the corresponding object that was constructed over here to do that we can accept a function as input to the second argument to dot attr and that function can take as input d which is the datum uh it's just a convention to use d to represent the datum like one row of the data or you know one entry in the data array and that can return d dot y because we've set it up here so when we access d dot y it's going to access the y value that we set up over there and we can do a similar thing for all the other attributes that we need width we can set it to dot width height we can set it to d dot height and mask we can set to d.mask and that's not doing anything now because i commented out the actual masks and as you can see it worked we've got our rectangles back this is how you can use d3 to create a bunch of rectangles on the screen now this duplication between here and here um is not ideal but i kind of like this way of doing things in a sense because it decouples the computation of the marks from the rendering of the marks this here it just creates an array of objects that we can directly pass in to all this d3 logic over here and i like this setup because if in the future you ever wanted to change what you were using for doing the dom manipulation you could and this part wouldn't have to change at all the only thing that would have to change is this like if you wanted to render it using react or view for example these other dom manipulation frameworks but this is not often what you see so often what you see is it's all combined into one big block and in a sense that's more efficient because there's no need actually to create all these strings if they're all the same and then refer to it here you know we could just pass in uh the value right here so what i'll do i just want to show you how that looks so i'm going to comment out this so it's there as a reference in the future and then refactor this to use the more common pattern that you find with d3 which is to to compute all the values in these accessor functions so instead of doing all this stuff here um i'll do it down here and if we're doing it like that we don't actually need all this logic to build up the array we can use a utility from d3 called range which creates an array range of say five it creates an array that has five integers in it that start at zero and to use this we need to import range from d3 along with select now that we've done console.log range of five you can see that it did output here that array that just contains some numbers so this is the data array that i'm going to use now as the input instead of saying data marks i'm going to say data range of 5. but we did have this variable called n which is the number of rectangles so i'm going to use that instead now in here instead of accessing the properties that were on those objects we can compute those values on the fly d is the number in this case so i'm just going to say d times 20. it can take the place of i and for these other ones i'm just going to return width and 10 for height and then this string of the circle mask right here and since these are returning the same values for each rectangle these don't actually need to be functions we could just pass in the value directly like this this highlights an important aspect of this dot attr method that it accepts either constant values like this or functions and when it when it accepts a function it passes in the object from the array into that function and then whatever that function returns it's going to use as the value for that attribute all right so we've got this far let's keep going in our refactoring of this code here what does this block do it creates a mask and it sets the id to be circle mask and it depends it to the svg we can do the same with d3 by saying svg dot append mask and we want to set the id attribute to be circle mask all right that should do the same thing as this block of code over here i don't know why it's not showing up our rectangles though oh oh yes because now that mask exists but it doesn't contain anything yet it doesn't contain those the circle or anything so that's our next step the next block of code adds that background rectangle of the mask let's do that now with d3 that would look like a mask dot append although we don't have mask yet um i'll just define that here to be that d3 selection of that mask element so now we could say mask dot append rect this one statement does the same thing as this statement here which creates the rectang element and also appends it to the mask parent now we can set the attributes width height and fill and if i change this to white it should show up all of our rectangles and it does okay but black means hide the rectangles this next block does the same thing but for a circle so i'm going to copy that logic for the rectangle and change it to be for a circle instead where we set the cy attribute we set the cx attribute and we set the radius attribute and finally we set the fill attribute of that circle all right and the mask is working again fantastic now let's do the same for mask 2 now remember mask 2 was all the same stuff as mask 1 except just the colors were inverted so we can copy this whole block of stuff mask 2 oops is a new mask that has the id circle mask dash 2. and then we append a rectangle to mask 2 and instead of it being black it should be white and then we append the circle to mask 2 down here and the fill of that should be black instead of white okay that seems to be working although i forgot to change it to mask 2. so i'll change that now mask 2 dot append mask 2 dot append all right and lastly we have our rectangles which i'll bring up to the top near the other rectangles just so all the rectangle stuff is in one spot so i'll paste that here and it's almost exactly the same as this block of code that we did for the first set of rectangles the only difference is that x gets set to i times 20. so i'll just change this y here to x width gets 10 height gets the value of the height variable and the mask is circle mask dash 2. okay see this breakage this is not what we expect see that so does anyone have um any idea what's going on here see what appears to be happening here is that this first chunk of code that made these rectangles it set up all of these rectangles properly but this line this next block of code that deals with rectangles it actually selected all of the rectangles that were there from the first block of code and then it set the x value of that and it set the height value um so it's just sort of a mess where our two different blocks of code are you know impacting the same set of rectangles which we don't want so how can we how can we make it so that these two different blocks act on different rectangles well there's a couple of approaches one approach is we could use classes to differentiate them and another approach is we could use group elements to contain them let me show you how both of those would play out if we want to use classes we can set the class attribute to be something like maybe horizontal and then when we say select all we can say rect.horizontal to only pick up on those horizontal ones and it's still broken because we haven't done the same treatment for the other set but we could say class is vertical over here and then we select on the vertical rectangles only this is one way of doing it i'm going to fork at this point just to have a reference of that state of things okay so this is one way of doing it using classes another way i'm just going to put it back to the way it was another way of solving this problem is to use group elements where you could say svg.append g first and that way it would only be working within this newly appended group element and an svg group element is it's a very um useful element it it just groups its children together in the dom tree and let me do the same for the other set okay now you can see it's it's not broken anymore and if we inspect the dom we can see that there's one group element that contains the set of rectangles going from top to bottom and then there's an entirely other group element that contains the rectangles going from left to right yeah and the reason why this works is because the selection for the rectangles only acts within each of these different group elements so that's another way to to solve that breakage okay very good uh this feels like a milestone from here i'm gonna fork this and try to reproduce that the full the full set with the different shapes any questions at this point yeah just a couple uh um the attributes uh the attr [Music] just so to get it straight in my head those the second arguments to the attr methods those effectively take match a data point in the um your array and put it on the screen basically that they they attach some kind of pixel point to that data point is is that the right way of thinking about these um these these arguments that are passed to attr yes yes that's correct that's exactly right so whatever we pass into dot data should be an array and the dot attr applies to all of the dom elements in that selection and so after we call dot join the first time this runs i mean it's not running multiple times but when it runs it's going to create a bunch of rectangles and then for each element of that array it's going to pass that element as the argument to this function that we've passed in and so the responsibility of this function is to map that entry in that array whatever it may be whatever shape it may be onto whatever is expected for the x coordinate in this case or in general the you know the attribute value of the dom element and so yeah it's a mapping function that essentially transforms the thing from data space whatever that may be into well i like to think of it as screen space in this case the x-coordinate so literal position on the screen but it could also be a color or you know width and height like these cases here but yes yes that's exactly what it's doing thank you yeah that makes sense and the earlier chunk where it talks about yeah append g um and select all and data are these so just so my mental model is correct um or as close to correct as possible it's recrea it's creating a brand new selection um at that early stage and what the are doing are modifying that selection is is that is that the way to think about it correct yeah some of these lines create a new selection and others of these lines modify an existing selection there was uh in the past i mean it still is there was a convention around indentation relating to this point and that convention was any line that creates a new selection be indented one level and then any line that modifies the exist the other you know modifies the selection that was defined on the line above should be indented by two spaces however that's sort of fallen out of fashion because everybody's using prettier nowadays but it's a nice touch and just to to really make it clear let me walk through each and every line of this svg.append g creates a new group element a dom element you know internally it invokes exactly this code of um you know document.createl with the svg namespace and it takes that that dom node that was constructed and associates it with this new newly minted d3 selection that didn't exist before and it creates a brand new selection so the return value from svg.append g is a selection that just contains that one group element when you say dot select all rect this creates yet another d3 selection that happens to be empty um it's it's an empty selection that would contain wrecked elements if there were wrecked elements there but there's not um this information the fact that it's empty is very useful to the internals of the dot data method which associates an array to that selection and at a certain point selections were made immutable which is a really nice feature of selection selections are immutable um so the dot data call creates a new selection where that empty selection of no rectangles is associated with this data all right and then when you say dot join rect this is actually a short form of uh another pattern which i'm not sure i totally want to get into but uh essentially it's dot enter dot append wrecked and so the enter selection handles the case where there's no dom element on the page corresponding to a given data element and so for each of those cases it will append a new rectangle so that's this this dot join is a short form for that in this particular case and then all of these lines that use dot attr they they don't actually mutate the the anything in javascript like the so the d3 selection of these rectangles is immutable however what it does when this line runs it goes through each and every dom element and it mutates the dom element it sets the y attribute to be the return value of you know whatever the value is returned from this function and same thing with all these others it goes through and updates the dom elements based on the return values so i hope that clarifies the the role of the different pieces definitely thank you very much my pleasure i see there's some questions in the youtube chat did join us to be enter rect well no it used to be dot enter dot append wrecked and join it actually does something with merge but we will get to that later um another question from the youtube chat since mask height and width are the same can you bind them to a parent g element that's interesting so the mask width and height are applied to the the rectangle within the mask not the mask element itself therefore um no i don't think you can do that i don't think you can do that all right um i'm about to dive into a whole other section maybe um let's take a quick five and then when we return i will uh go all out in trying to build this thing all right yeah let's take five see you in five minutes you um and my questions are right here you all right i'm back uh before we move on are there any final um doubts about what's happening so far what are we creating exactly oh we are creating this piece with the shapes yeah in order okay yeah and in order to have the flexibility to do this i've started to use d3 because it's going to make it a lot easier well it's going to make it possible if we were to try to do it with the raw dom api it would be you know really a big mess okay here we go let's um let's fork this one and i'll call it uh full solar with reproduction with d3 the first thing i'm going to do here is just delete all this junk that is left over from earlier deleting all these blocks that are commented out all right now we're left with this program that is 62 lines of code what we can do next is change this mask to be a shape and d3 actually provides a set of symbols that we can use there is a package called d3 shape and in d3 shape there is a section on symbols these symbols here are provided by d3 and the way this plays out is that you call some d3 functions and it generates for you the string to use as the d attribute for a path to create one of these symbols on the screen we need to just make a path element and then set the d attribute to the the return value from some invocation let's see what that invocation would be we have to call d3.symbol and pass in type and size the type that we pass in should be one of these symbol types like d3 dot symbol circle d3 dot symbol cross d3 dot symbol diamond also the set of symbols is exposed as d3 dot symbols this is an array that contains all of these individual things and so since our aim is to reproduce this piece which has a bunch of different symbols going across i'd say we can just use that array of all the symbols that are available and you know traverse it use that as our data all right so let's import symbols from d3 in our code import select range and symbols from d3 we also need to import symbol from d3 the way we use this is we call symbol passing in a type and a size and that returns a symbol generator which we then need to invoke with some arguments or no arguments i guess works and that there's some just there's just something complicated um that you could do if you pass an argument but you don't need to pass an argument so once you invoke this function it will return that that string that we can use as the d attribute of a path so let's give it a shot on one of these masks i'm going to replace this circle with a path and instead of cxcy and r we can set the d attribute to be symbol and i'm going to pass in the symbol type which i can just access you know symbols at index zero the first entry of the symbols array and the second argument here could be the size um i don't really know what the range of size is i'll try 300 and this invocation here returns a symbol generator which we then need to invoke as a function and once we do it should return some stuff all right i don't see anything maybe the size needs to be increased there it is i see something start to appear yeah okay there it is um the first symbol happens to be a circle but to see some other shape we can access i don't know the next symbol symbols at index one for example notice that the symbol is centered in the upper left that's i think the first problem i would like to address here um yeah and the way we need to do this is well we want to move it around my first goal here is to move it so that it's in the center and to do that i think the most straightforward way to would be to use an svg group element as the parent of this path and transform that group element meaning move that group element around with everything in it let's try that all right so here we've got mask dot append path but instead what if we did mask dot append g dot append path this works just the same as it did because it's just a grouping with no transform applied to it to apply the transform to this group element to move this path around we can say dot attribute transform and the value will be translate of let's just try some numbers 400 comma 400 yeah that moved it the way this works is um it's a string but it looks like a function invocation it's it's a domain specific language for transforms and what it looks like is the first argument to this function s kind of a thing in a string is the x coordinate so it's going to move it over to the right by 400 pixels and the second argument here is the y coordinate so it's going to move it down by 400 pixels ideally to put it in the center we could say width divided by 2 and height divided by 2. but since this is a string that's not going to work we can take advantage of an es6 language feature called string template literals and the way we can use this is by using backticks and then inside of this thing we can bust out into javascript using this syntax here a dollar symbol and then a pair of curly braces so now whatever is inside this pair of curly braces will be evaluated as javascript and inserted into that location in the string so now we can actually use width over 2 and height over 2. and now it's dead center look at that i'll bring the size down a little so it fits on the screen there we go all right so the problem now is that one of these masks uses this symbol and the other mask still uses the circle so i mean one thing we could do to address that is copy paste all this code which is what i'll do for now and change white to black all right now this sort of looks is starting to resemble that solar wood piece a little bit more but i hate this duplicated logic between these two blocks these two masks i mean there's a bunch of code that looks almost exactly the same the only difference is one of these is filled with white and has an id of circle mask and the other is filled with black and has an id of circle mask 2. and now that i think of it these names are a little outdated um because it's not circles anymore so i'm going to rename it to mask 1 and mask 2. and i have to go and update the references as well okay now what i would like to do before we go any further is refactor this so that there's no duplicated logic the reason being once we start adding more shapes into here we're going to have to copy paste larger and larger chunks and if we want to change it in one place we'd have to change in two places it would be a total nightmare and so this is a point at which refactoring makes a lot of sense refactoring is this notion of reorganizing code so that it's more maintainable or you can do more things with it generally refactoring is triggered when you see duplicated logic in your code all right so what i'm going to do here is define a function and the idea is we can invoke this function and and we can pass in only the things that vary between the two namely the mask id which is different and the fill which is different so let's try that const what should i call it render mask equals a function and i'm just going to take all this logic that appends the rectangle and the path and put it inside these curly braces and use prettier to automatically indent that now the things that we want to accept here are the id and the fill oh but first of all we should actually invoke this function okay now everything is in working order again and we can take as as one argument here the id of the mask and we can use it here when we're setting the id so i'll say render mask and pass in that string mask dash 1 here which surfaces inside of this function as the local variable called id which is used here to set the attribute id on the mask right so we can actually now call render mask twice passing in different ids and i'll just comment out this other block because it's going to be replaced by this function all right so the the fill is the other thing that we we need to have vary between these two function calls so i think what i'll do is uh instead of white this will be fill which we can accept as the second argument and so what we can do here is pass in white to the first one and black to the second one although this is not exactly right because we also need to deal with the the background rectangle which needs to be inverted like if it's black it needs to be white so you know what instead of using fill i'm going to accept an argument called inverted and this this will be a boolean where if it's true the colors should be inverted if it's false they shouldn't be and so inverted for the first invocation should be false and for the second invocation should be true and then we want the rect fill to be black if it's not inverted and white if it is and to do that i see there's some youtube chatter let me finish this thought and i'll answer some questions there to do that the tool that i would reach for is the ternary operator so we could say inverted question mark if it is inverted this should be white otherwise it should be black since this is the first time i'm using this i want to stop and show you what it would look like without this syntax it would look kind of like this let rect fill equals black and then we could say if inverted wrecked fill equals white and then we would use rectfill down there so what it's doing is if it's not inverted this code will not run and wrecked fill will be black but if inverted is true erect fill will be white and this will be filled in with white however i think the cleaner syntax would be to use the ternary operator and say if it's inverted it should be see now i'm losing track you know which what should it be let's say black and white now we can apply the same logic to the fill of the path but it should be inverted from the background and so if it's inverted now it should be white if it is and black if it isn't okay there we go victory now we can get rid of this commented out stuff here and now there's just one block of code that is responsible for rendering this mask the first time we render it as mask 1 which is not inverted and the second time we render it as mask 2 which is inverted and at this juncture i want to introduce the the syntax of calling functions on d3 selections what that looks like is it can take this is just a convention that i want to introduce now it's a convention that's used when you start to use functions to abstract to build abstractions in this d3 world so what it looks like is we pass the selection as the first argument and inside of this function we refer to it generically as some selection and we would pass it in as svg here as the first argument now it works everything works as expected and the conventional way of doing these sorts of invocations with d3 would be to use selection.call so we could say svg.call render mask and then pass in these second two arguments here like this and we can invoke it again with these other arguments these two are exactly equivalent in terms of what they do i just wanted to introduce this way of invoking functions on selections it doesn't have any immediate benefit right now but it can be a very nice elegant way of doing it one of the things one of the reasons being that you can chain it like this so this is also valid syntax all right so i'll fork from here just to capture the state of things now the task before us is to render multiple symbols in here instead of just one so how can we do that well we've got symbols at index 1 in use here and what we essentially want to do is iterate through so if i change it to be symbols at index 2 the shape that you see is a different shape and symbols that index four is a star for example what we want to do is append multiple paths where each path has the this d attribute from a different symbol so how can we do this instead of just appending a single path we can use this data join pattern that we used earlier with our rectangles what this looks like is well instead of doing it on the end of here i'm going to assign a variable called g just to sort of separate these concerns here so g is our parent we can say g dot select all paths and initially it's going to be empty dot data and what should the data be here what i'm thinking is it can just be the same thing we did with the rectangles we can use the range utility to create an array that contains integers starting at zero and how many things should be in here well it should be the same as the number of symbols that are available which we can access from symbols dot length all arrays have this dot length property and it it returns to you the number of elements that are on the array all right so we've got this array of integers going from 0 to the number of symbols so now what we need to do is use this dot join method on paths now it's going to create a bunch of paths and on each path we want to set the d attribute to be something like this that we had earlier but we want it to vary based on the symbols and we also want the fill to be white or black since we are making multiple paths we can use a function here to take as input d one of these integers and we can access symbols at index d to have it be different symbols and what we get here is like this giant splotch where all the symbols are right on top of each other so the task at hand here now that we've got this far is to split these apart in the x direction so that you know one symbol is on the left and one symbol is on the right since we have d as an integer that starts at zero goes upward we could potentially move each of these group elements over if we had them but we don't we're just creating path elements and there's a containing parent group element i think what we really want is to have it so that for each symbol there is a separate group element created and then within that group element there should be a path yeah to clarify what i'm what i'm saying if we inspect the dom here we can see that within the mask there's a group element that has a transform and then within that group element there are a bunch of paths i think instead we want to invert this containment relationship where for each symbol there should be a parent group element that gets a different transform that gets translated differently in the x direction and then within each one of those group elements we can put the path yeah that's what i'm going to go for and we could keep this parent element but i don't think it's necessary because we could just set the transform on the inner elements so i'm going to get rid of that for now and instead of appending our paths to g i'm going to append them to mask now it's working again but it's it's in the upper left corner because you know they're at the origin now next step here is to use group elements instead of paths so i'm going to say mask dot select all g dot data dot dot dot join g however this this convenience method is no longer enough for this case that we've got now we need to deal with this concept of the enter selection because what we want to do is tell d3 whenever you create a new group element you should also append a path element inside of it yeah and to do that we need to use the join method in a slightly different way what we've seen so far is the shorthand of join where it accepts a string being the tag name but the long form of dot join is where it accepts arguments for enter update and exits and i'll get more into this later but the simplest form of this is we pass a function to dot join and it's interpreted as the enter selection actually more precisely we need to pass a function that takes as input the enter selection all right i realize it is a little confusing and in here we can say enter dot append g to create our parent group element but on this we can say dot append path and then on these paths is where we can set the d attribute and the fill attribute okay we're back in working order just to work through to walk through this one more time we're passing a function to dot join that function will be invoked with the enter selection of this data join and because there are no group elements initially the enter selection will will have this append method that will get triggered for each and every entry in the array so we append a group element and we append a path element to each of those group elements and we set d and fill on each of those paths so now if we inspect the dom and look at it that way we can see that there are indeed many group elements it's just that what we need to do now is transform each of these group elements to translate them so that they're in the middle vertically but they're spreading across the page horizontally and i see in the youtube chat vonnie says use this code right here okay i'm going to copy that code and see if it works thanks for that i'm just pasting this from the youtube chat oh that's that's what i've got already but yes that's correct that's correct um all right what we want to do here is for these group elements we want to set the attribute of transform to be we can take this thing that we can had constructed earlier translate by width over two height over two okay now it's in the middle again but we don't actually want to translate in the x direction by width over 2 we want to translate in the x direction by some function of d and keep in mind d is an integer that goes between 1 and however many symbols there are let's just say d times 200 to like spread them out by 200 oh but this is not a function it's just a string literal so let me make it into a function that accepts d okay there we go there we go all right it's starting to come together that looks like pretty cool abstract art right there oh vani says it's not the same it uses i let me see that let me take a look at that yeah so this is the code that i wrote this is the code from vani in youtube it's it is slightly different it accepts i yeah this when you pass a function it does take um a second argument which is the index in the array so this works as well just the same however it just so happens that the array is integers that would be the same as i all the time so that's why this this variant works but anyway um we can just tweak these numbers to make it so that different shapes show up at this point so they seem to be a little too large what if i say you know the size is 50 000 and by the way i think this size is like the number of fields filled in pixels i don't know it's it's just they're high numbers but i don't know this is just what what it takes so i'll bring down the size until we can see multiple shapes there we go starting to starting to come together and i'm just guessing here at the this constant that will split them apart you can make it even smaller all right this is almost there look at that beautiful beautiful any questions at this point so the um example of the join in this case that was um a case of appending um elements to multiple elements um which was different from before um is that right correct yeah it's it's a slightly different pattern and it's using the the long form of dot join so that we can access the enter selection yeah so it passes in the enter selection and the enter selection is populated with the case where there is no corresponding group element for a given data element which is the case for all of them when this runs that's why enter dot append g will append a group element for each and every entry in our array here for each and every symbol and then after appending that group element and setting the transform we append to that group element a path and then dot attr d this whole thing works because of some the way d3 is set up it actually uses the the data element that was bound to the parent so that's why this works the data element d which is one of those integers is associated with the group element and then if you append to it if it came from the enter selection you can you can reference those those data elements from the child so that that's why this this uh works but i realize it is a lot at once in in for today um but that's sort of the intention to just like show you know the scope of this d3 data join thing um and it will hopefully become more clear over time but does that answer your question uh yes thank you yeah that's really clear thank you nice nice and by the way this this dot join method is is new to me i mean i've i i learned really well the old syntax with the dot enter and the dot merge um so for what it's worth i really only learned this this pattern recently and i'm still sort of you know trying to wrap my head around it so yeah feel free to interrupt me as i go and ask questions like this this is great this is great oh my gosh oh look at it what time is it hang on oh look at the time i'm overtime i'm so sorry all right well we've got pretty far for today uh i just want to wrap this up by tweaking these numbers a little bit so that there's some space along the side see how this circle is right up against the edge there i can just add some constant to this like a hundred yeah that looks decent and then i'll bring this number down so that there's some space on the right yeah like 125 or so and i'll bring the size down just a bit to make sure that we can see all the shapes and in viz hub you can set the height so i'm going to make it maybe 200 pixels so that it more closely resembles the original piece okay there we have it there we have it that's all we're going to do for today i will leave it as an exercise to make the back sort of scratch that to make the background rectangles match the original solar wood piece so here's your exercise and this is this is um kind of a challenging one see how in this original piece each subsequent square reorients the lines however in our example what we've done is they're all the same so that's one thing you could do as an exercise alternatively um i i would encourage you to use d3 to try to reproduce any other solute piece because now that we have this this new tool in our toolbox of the d3 data join uh it'll become a lot easier to reproduce these solar with pieces or create art just generative art in the spirit of solo wit and just as a reminder here's some other solo with pieces that you could do with the tools that we have now the svg mask and the paths this one for example could be done with nested for loops and math.random to pre produce the points so yeah that's all for today thanks everyone for joining and i'll see you next week take care
Info
Channel: Curran Kelleher
Views: 865
Rating: undefined out of 5
Keywords:
Id: iX_Q17VYrWs
Channel Id: undefined
Length: 108min 10sec (6490 seconds)
Published: Sat Mar 27 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.