{"id":20207003,"url":"https://github.com/tomgp/ft-workshop-ex2","last_synced_at":"2026-05-10T01:15:23.693Z","repository":{"id":142289593,"uuid":"44377066","full_name":"tomgp/FT-workshop-EX2","owner":"tomgp","description":null,"archived":false,"fork":false,"pushed_at":"2015-11-18T10:41:21.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-13T20:49:50.970Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomgp.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-10-16T09:58:22.000Z","updated_at":"2015-10-16T10:02:04.000Z","dependencies_parsed_at":"2023-03-13T18:42:23.711Z","dependency_job_id":null,"html_url":"https://github.com/tomgp/FT-workshop-EX2","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgp%2FFT-workshop-EX2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgp%2FFT-workshop-EX2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgp%2FFT-workshop-EX2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomgp%2FFT-workshop-EX2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomgp","download_url":"https://codeload.github.com/tomgp/FT-workshop-EX2/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241644543,"owners_count":19996177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T05:27:05.978Z","updated_at":"2026-05-10T01:15:23.655Z","avatar_url":"https://github.com/tomgp.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"Second exercise for a D3 workshop running at the Financial Times\n\n * __Prerequisit:__ Some Javascript, some D3 (selections, )\n * __Outcome:__ After doing this excercise you should an idea about making javascript modules to keep your code in manageable chunks.\n * __Outcome:__ Also you'll appreciate the benefits of D3 selections `call` and `each` methods\n \n---\n\n##Modules\nModules are a way of splitting code into different files which are self sufficient and resuable. They help to keep your main program files clear of distratcion so it's easier for you to think about the overall structure of your programs. Javascript doesn't support this kind fo thing in the browser right now but there are tools which will allow you to write your javascript in separate files and then pulls them all together. In the tintreractive team we use [browserify](http://browserify.org/) which mirrors the syntax of Nodejs modules. So for example if you want to use D3 in you a can type...\n\n```\nvar d3 = require(d3);\n```\n\nAnd (providing that module is installed) it can be included in your code.\n\nThis repository has a basic build script set up to do this process for you -- it's like a cut down version of our [project starter kit](https://github.com/ft-interactive/project-starter-kit). So: \n\n * clone this repository from GitHub. [Here it is](https://github.com/tomgp/FT-workshop-EX2)  \n * go to the directory into which you cloned it and type `npm install`. This will make available various dependencies (like the aforementioned 'browserify') \n * type `npm run watch` to start a development server and rebuild your javascript on the fly\n\nRight. You may notice the repository you just cloned has several branches. These are completed steps of the excercise so if you go off track you can get back on track fairly easily by just switching to the appropriate branch. Or if you want you can just skip straight to the branch and mess around with the code to see what happens.\n\n###My first module\n\nWe're going to write some code and then turn it into a module.\n\nAt the moment the file `source/main.js` has some very simple code in it to load a CSV and creates a date formatter (which matches the format used in th data) using [D3's time formatter](https://github.com/mbostock/d3/wiki/Time-Formatting)\n\nThe data in question is a survey I made up where we imagined asking some people for the their opinion on something and then tallied up their imaginary answers , Yes or No, across various dates. Lets try and visualise this as a series of multiple small charts... \n\nFirst thing we need to do is sort out the data, the dates need to be proper dates not just strings and it would be useful to have the survey results as percentages. the easiest way to do the same thing to each element of an array is to use [Javascript's map function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).\n\nhow about something like ...\n\n```\nvar processedData = data.map(function(d){\n\tvar dateFormat = d3.time.format('%Y-%m-%d');\n\tvar total = Number( d.yes ) + Number( d.no );\n\treturn {\n\t\tdate: dateFormat.parse( d.date ),\n\t\tyes: Number( d.yes ),\n\t\tno: Number( d.no ),\n\t\ttotal: total,\n\t\tyesPct: 100/ total * Number( d.yes ),\n\t\tnoPct: 100/ total * Number( d.no )\n\t};\n});\n\n```\n\n####BRANCH : process-data\n\nThe good thing about using `map` rather than a comventional `for` loop is that it's easier to see that the function has no side effects, it takes an input and returns an output but nothing outside the function is changed. This makes it easier to think about the function spearately from the surrounding program. \n\nIt's definately worth familiarising yourself with javascript's built in array functions [here's a page which lists them](https://github.com/mbostock/d3/wiki/Arrays).\n\nSo that's our code for processing the data -- in real world examples you may be doing somethign more complex but for our purposes this is fine.\n\nBecause the thing that does the data processing is a function with (we hope) no side effects we can easily take it out from its current place. \n\nLets do it in stages so that we can see what's happening\n\nFirst lets turn the anonymous function used in the map into a named function\n\n```\nvar processedData = data.map(processElement);\n\nfunction processElement(d){\n\t...\n}\n```\n\nSo now we have a function called process element let's put that in a new file. Make a new file in the same directory as _main.js_ called something sensible like _data-processing.js_ cut the `processElement` function from _main.js_ and paste it in there. \n\nAt this point your browser console will (I hope) give an error as ```processElement``` is no longer defined as far as _main.js_ is concerned.\n\nThere are a couple of things we need to do to fix this state of affairs.\n\nWe need to make _data-processing.js_ make  the `processElement` function available to the outside world. THis is done as follows:\n\n```\nfunction processElement(d){\n\t...\n}\n\nmodule.exports = processElement;\n```\n\nThen in _main.js_ we need to specify that this file is a dependency\n\n```\nvar processElement = require('./data-processing.js');\n```\n\nBut wait! We're getting a different error now. This is because the function we made isn't as nicely isolated from the world outside as I tried to make you think it was. Because of the date processing D3 is a dependency of _data-processing.js_. There are a couple of ways to fix this the easiest being simply to require D3 inside _data-processing.js_. \n\n```\nvar d3 = require('d3')\n```\n\nOtherwise we could a) inline or rewrite the bit of code we are using from d3 might be worth it if we expect this may be used in a d3-less context or b) use a technique called dependency injection where we give the modules user a mechanism by which they can pass a date parser of their own creation into the function (that's a bit beyond what I'm hoping to explain here though)\n\n####BRANCH : make-a-module\n\nOK, now lets skip some basic drawing stuff so switch to\n\n####BRANCH : draw-some-stuff\n\nand we'll go from there.\n\n###Using 'call'\n\nIf you want to add multiple nodes to a particular parent e.g. a _text_ label and a _rect_ angle to a _g_roup you might typically store the selection chain at a particular point to a variable and then continue adding stuff to that variable, something like this:\n\n```\nvar parent = d3.select('svg').selectAll('g').data(data)\n\t.enter()\n\t\t.append('g')\n\nparent.append('text')\n\t...\n\nparent.append('rect')\n\t...\n\n```\n\nthis is nice andeasy and perfectly fine for simple situations but it deosn't allow for easy code reuse; say the visualisation you're adding to the group needs to be repeated in otehr contexts, you might be able to encapulate it as a function -- something that took the parent selection as an argument and returns the current selection.\n\nthe function might look something like this\n\n```\n\nfunction( parent ){\n\tparent.append('text')\n\t\t...\n\n\tparent.append('rect')\n\t\t...\n}\n\n```\n\nluckily D3's selection has a method ```call``` which allows you to do exactly this\n\ni.e.\n\n```\nd3.select('svg').selectAll('g').data(data)\n\t.enter()\n\t\t.append('g')\n\t.call(function( parent ){\n\t\tparent.append('text')\n\t\t\t...\n\n\t\tparent.append('rect')\n\t\t\t...\n\t});\n```\n\n\n#### branch: 'using-call'\n\nPersonally, in-spite of it's other advantages, I think this code is less readable than the simple approach we took first time around but we can make it much better by extracting the currently anonymous _call_ed function. Like this:\n\n```\nd3.select('svg').selectAll('g').data(data)\n\t.enter()\n\t\t.append('g')\n\t.call(drawVisualisation);\n\nfunction drawVisualisation( parent ){\n\tparent.append('text')\n\t\t...\n\n\tparent.append('rect')\n\t\t...\n}\n```\n\ndoing this lets us consider the function as a 'black box'; we know what inputs it takes and what outputs it gives back so the actual process of visualisation can be thought about separately. We might decide later in the project that that we want to use two labels and a circle or anything else, it's fine.\n\n#### branch: 'extract-call-function'\n\n#### branch:'using-each'\nuse each to render different cuts of the same data on the page \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomgp%2Fft-workshop-ex2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomgp%2Fft-workshop-ex2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomgp%2Fft-workshop-ex2/lists"}