{"$schema":"https://ejfox.com/schema/page-twin@1.json","kind":"blog_post","url":"https://ejfox.com/blog/2019/how-i-prototype-apps-and-dataviz-quickly-in-2019","json_url":"https://ejfox.com/blog/2019/how-i-prototype-apps-and-dataviz-quickly-in-2019.json","generator":"ejfox.com/json-twin@1","data":{"cacheVersion":"2026-05-13-gear-cards","html":"<h2 class=\"\" id=\"how-i-prototype-apps-and-dataviz-quickly\">How I prototype apps and dataviz quickly</h2>\n<p class=\"\">I’ve been really enjoying using Vue as a framework for my javascript applications. It allows me to quickly and easily lay out my data structure, some DOM-elements that are controlled by data, and handle any interactions / application state stuff I might need.</p>\n<p class=\"\">In years past, my development workflow usually started with me cloning my <a href=\"https://github.com/ejfox/starter\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://github.com/ejfox/starter\">@ejfox/starter<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" class=\"\"></path></svg></a> project, which has gulp, webpack, and a handful of other useful frameworks for making things quickly already set up.</p>\n<p class=\"\">Now, since it’s upgrade to 3.0, I pretty much exclusively use</p>\n<p class=\"\"><code class=\"md-inline-code\">> vue create project-name</code> which gives you a little CLI wizard to create a new Vue project. Importantly, you can manually select the features you want and create your own project defaults.</p>\n<p class=\"\">Mine, for example, automatically pulls in the Router, Vuex, Linter, and Stylus.</p>\n<p class=\"\">I <a href=\"https://www.youtube.com/watch?v=Q6e4pQccMH4\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://www.youtube.com/watch?v=Q6e4pQccMH4\">recorded a video of myself spinning up a prototype<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M23.498 6.186a3.02 3.02 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.02 3.02 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.02 3.02 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.02 3.02 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814M9.545 15.568V8.432L15.818 12z\" class=\"\"></path></svg></a> of a D3 force layout using the techniques I describe below, it’s around 90 minutes but you can tell YouTube to play it double-speed and click around - if you’re curious.</p>\n<h3 class=\"\" id=\"pulling-in-data\">Pulling in data</h3>\n<p class=\"\">In my old starter project, I would pull in data with <a href=\"https://github.com/ejfox/starter/blob/master/src/coffee/app.coffee#L19\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://github.com/ejfox/starter/blob/master/src/coffee/app.coffee#L19\">d3.queue<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" class=\"\"></path></svg></a> which was super easy and very little code.</p>\n<p class=\"\">With Vue as my default framework, I’ve moved over to using the <a href=\"https://vuejs.org/v2/cookbook/using-axios-to-consume-apis.html\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://vuejs.org/v2/cookbook/using-axios-to-consume-apis.html\">pattern of using axios to update objects in the component’s data<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M2 3h3.5L12 15l6.5-12H22L12 21zm4.5 0h3L12 7.58L14.5 3h3L12 13.08z\" class=\"\"></path></svg></a> which is also super easy and very little code. Check out this super-simple example of axios+vue which powers ejfox.com/vibes.</p>\n<p class=\"\">When I’m pulling data from an API, it’s as simple as setting up the keys, building the API call, looking at the returned data in the Vue Chrome Devtools Inspector, and then building out the DOM elements with Vue’s <a href=\"https://vuejs.org/v2/guide/list.html\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://vuejs.org/v2/guide/list.html\">v-if and v-for<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M2 3h3.5L12 15l6.5-12H22L12 21zm4.5 0h3L12 7.58L14.5 3h3L12 13.08z\" class=\"\"></path></svg></a> directives.</p>\n<h3 class=\"\" id=\"combining-vue-and-d3-for-dataviz\">Combining Vue and D3 for dataviz</h3>\n<p class=\"\">Part of the reason that I really like using Vue is that it meshes with my understanding of D3, which I use to create a majority of my data visualization work both personally and professionally.</p>\n<p class=\"\">Vue makes it so that we no longer need our old, trusty friend <code class=\"md-inline-code\">d3.select().enter().append().exit()</code> to bind data to various DOM elements, since Vue is doing it for us.</p>\n<p class=\"\">Before Vue, I would have an SVG already on the page and do something like</p>\n<figure data-rehype-pretty-code-figure=\"\" class=\"\"><pre class=\"md-pre\" style=\"background-color:#09090b;color:#cccac2\" tabindex=\"0\" data-language=\"js\" data-theme=\"\"><code data-language=\"js\" data-theme=\"\" style=\"display: grid;\" class=\"md-codeblock\"><span class=\"\" data-line=\"\"><span style=\"color:#FFAD66\" class=\"\">const</span><span style=\"color:#CCCAC2\" class=\"\"> svg </span><span style=\"color:#F29E74\" class=\"\">=</span><span style=\"color:#CCCAC2\" class=\"\"> d3</span><span style=\"color:#F29E74\" class=\"\">.</span><span style=\"color:#FFD173\" class=\"\">select</span><span style=\"color:#CCCAC2\" class=\"\">(</span><span style=\"color:#D5FF80\" class=\"\">\"svg\"</span><span style=\"color:#CCCAC2\" class=\"\">)</span><span style=\"color:#CCCAC2B3\" class=\"\">;</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFAD66\" class=\"\">const</span><span style=\"color:#CCCAC2\" class=\"\"> circles </span><span style=\"color:#F29E74\" class=\"\">=</span><span style=\"color:#CCCAC2\" class=\"\"> d3</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#F29E74\" class=\"\">  .</span><span style=\"color:#FFD173\" class=\"\">selectAll</span><span style=\"color:#CCCAC2\" class=\"\">(</span><span style=\"color:#D5FF80\" class=\"\">\"circle\"</span><span style=\"color:#CCCAC2\" class=\"\">)</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#F29E74\" class=\"\">  .</span><span style=\"color:#FFD173\" class=\"\">data</span><span style=\"color:#CCCAC2\" class=\"\">(myData)</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#F29E74\" class=\"\">  .</span><span style=\"color:#FFD173\" class=\"\">enter</span><span style=\"color:#CCCAC2\" class=\"\">()</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#F29E74\" class=\"\">  .</span><span style=\"color:#FFD173\" class=\"\">append</span><span style=\"color:#CCCAC2\" class=\"\">(</span><span style=\"color:#D5FF80\" class=\"\">\"circle\"</span><span style=\"color:#CCCAC2\" class=\"\">)</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#F29E74\" class=\"\">  .</span><span style=\"color:#FFD173\" class=\"\">attr</span><span style=\"color:#CCCAC2\" class=\"\">((</span><span style=\"color:#DFBFFF\" class=\"\">d</span><span style=\"color:#CCCAC2\" class=\"\">) </span><span style=\"color:#FFAD66\" class=\"\">=></span><span style=\"color:#CCCAC2\" class=\"\"> {</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFAD66\" class=\"\">    return</span><span style=\"color:#CCCAC2\" class=\"\"> {</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#CCCAC2\" class=\"\">      r</span><span style=\"color:#CCCAC2B3\" class=\"\">:</span><span style=\"color:#CCCAC2\" class=\"\"> d</span><span style=\"color:#F29E74\" class=\"\">.</span><span style=\"color:#CCCAC2\" class=\"\">radius</span><span style=\"color:#CCCAC2B3\" class=\"\">,</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#CCCAC2\" class=\"\">      cx</span><span style=\"color:#CCCAC2B3\" class=\"\">:</span><span style=\"color:#CCCAC2\" class=\"\"> d</span><span style=\"color:#F29E74\" class=\"\">.</span><span style=\"color:#CCCAC2\" class=\"\">x</span><span style=\"color:#CCCAC2B3\" class=\"\">,</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#CCCAC2\" class=\"\">      cy</span><span style=\"color:#CCCAC2B3\" class=\"\">:</span><span style=\"color:#CCCAC2\" class=\"\"> d</span><span style=\"color:#F29E74\" class=\"\">.</span><span style=\"color:#CCCAC2\" class=\"\">y</span><span style=\"color:#CCCAC2B3\" class=\"\">,</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#CCCAC2\" class=\"\">    }</span><span style=\"color:#CCCAC2B3\" class=\"\">;</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#CCCAC2\" class=\"\">  })</span><span style=\"color:#CCCAC2B3\" class=\"\">;</span></span><button type=\"button\" title=\"Copy code\" aria-label=\"Copy code\" data=\"const svg = d3.select(&#x22;svg&#x22;);\nconst circles = d3\n  .selectAll(&#x22;circle&#x22;)\n  .data(myData)\n  .enter()\n  .append(&#x22;circle&#x22;)\n  .attr((d) => {\n    return {\n      r: d.radius,\n      cx: d.x,\n      cy: d.y,\n    };\n  });\" class=\"rehype-pretty-copy\" onclick=\"navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);\"><span class=\"ready\"></span><span class=\"success\"></span></button><style class=\"\">:root {--copy-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E\");--success-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E\");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}</style></code></pre></figure>\n<p class=\"\">But now with Vue, I just use the HTML declarations in the template like - because of the built-in reactivity, when the data changes the DOM elements are updated. No need for the old D3 select, enter, append pattern.</p>\n<figure data-rehype-pretty-code-figure=\"\" class=\"\"><pre class=\"md-pre\" style=\"background-color:#09090b;color:#cccac2\" tabindex=\"0\" data-language=\"html\" data-theme=\"\"><code data-language=\"html\" data-theme=\"\" style=\"display: grid;\" class=\"md-codeblock\"><span class=\"\" data-line=\"\"><span style=\"color:#5CCFE680\" class=\"\">&#x3C;</span><span style=\"color:#5CCFE6\" class=\"\">svg</span><span style=\"color:#5CCFE680\" class=\"\">></span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#5CCFE680\" class=\"\">  &#x3C;</span><span style=\"color:#5CCFE6\" class=\"\">circle</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">    v-for</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"circle in myData\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">    :r</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"circle.radius\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">    :cx</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"circle.x\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">    :cy</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"circle.y\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#5CCFE680\" class=\"\">  /></span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#5CCFE680\" class=\"\">&#x3C;/</span><span style=\"color:#5CCFE6\" class=\"\">svg</span><span style=\"color:#5CCFE680\" class=\"\">></span></span><button type=\"button\" title=\"Copy code\" aria-label=\"Copy code\" data=\"<svg>\n  <circle\n    v-for=&#x22;circle in myData&#x22;\n    :r=&#x22;circle.radius&#x22;\n    :cx=&#x22;circle.x&#x22;\n    :cy=&#x22;circle.y&#x22;\n  />\n</svg>\" class=\"rehype-pretty-copy\" onclick=\"navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);\"><span class=\"ready\"></span><span class=\"success\"></span></button><style class=\"\">:root {--copy-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E\");--success-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E\");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}</style></code></pre></figure>\n<p class=\"\">I still use a ton of D3 functions though, <a href=\"https://github.com/topojson/topojson-client\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://github.com/topojson/topojson-client\">topojson-client<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" class=\"\"></path></svg></a>, all the D3 number and color scales, force layouts, etc.</p>\n<h4 class=\"\" id=\"animating-enteringexiting\">Animating entering/exiting</h4>\n<p class=\"\">If I want to fade elements in and out like you would do with <code class=\"md-inline-code\">d3.select(selection).enter().transition(t).style(‘opacity’, 1)</code> I’ve been using <a href=\"https://daneden.github.io/animate.css/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://daneden.github.io/animate.css/\">animate.css<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M14.435 12.973c.269 0 .492.133.686.396c.192.265.294.588.294.975c0 .385-.102.711-.294.973c-.193.265-.417.396-.686.396c-.278 0-.522-.131-.715-.396c-.192-.262-.294-.588-.294-.973c0-.387.102-.71.294-.975c.192-.264.436-.396.715-.396m3.44-3.559c.746.811 1.125 1.795 1.125 2.953q0 1.122-.259 2.023q-.263.896-.654 1.459a4.3 4.3 0 0 1-.974.989a5 5 0 0 1-1.065.623a5.5 5.5 0 0 1-1.111.306a9 9 0 0 1-.943.123l-.685.014l-.547.015a18 18 0 0 1-1.524 0l-.547-.015l-.685-.014a9 9 0 0 1-.943-.123a5.3 5.3 0 0 1-1.111-.306a5 5 0 0 1-1.064-.623a4.3 4.3 0 0 1-.975-.989q-.392-.563-.654-1.459q-.26-.9-.259-2.023c0-1.158.379-2.143 1.125-2.953q-.123-.062-.008-1.217a7 7 0 0 1 .495-2.132c.934.099 2.09.629 3.471 1.581c.466-.119 1.101-.183 1.917-.183q1.278.002 1.918.184q.943-.638 1.805-1.034c.584-.261 1.005-.416 1.269-.457l.396-.09q.404.974.496 2.132q.114 1.154-.009 1.216m-5.845 7.82c1.688 0 2.954-.202 3.821-.607c.855-.404 1.292-1.238 1.292-2.496q0-1.096-.822-1.828a1.85 1.85 0 0 0-.989-.486q-.563-.091-1.72 0q-1.155.091-1.582.09c-.385 0-.8-.018-1.319-.059q-.78-.06-1.223-.078a3.7 3.7 0 0 0-.958.108a1.9 1.9 0 0 0-.853.425c-.521.469-.79 1.077-.79 1.828q0 1.888 1.28 2.496c.85.405 2.113.607 3.802.607zm-2.434-4.261c.268 0 .492.133.685.396c.192.265.294.588.294.975c0 .385-.102.711-.294.973c-.192.265-.417.396-.685.396c-.279 0-.522-.131-.716-.396c-.192-.262-.294-.588-.294-.973c0-.387.102-.71.294-.975c.193-.264.436-.396.716-.396\" class=\"\"></path></svg></a> in combination with <a href=\"https://vuejs.org/v2/guide/transitions.html#Custom-Transition-Classes\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://vuejs.org/v2/guide/transitions.html#Custom-Transition-Classes\">Vue’s custom transition classes<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M2 3h3.5L12 15l6.5-12H22L12 21zm4.5 0h3L12 7.58L14.5 3h3L12 13.08z\" class=\"\"></path></svg></a>. So I’ll do something like</p>\n<figure data-rehype-pretty-code-figure=\"\" class=\"\"><pre class=\"md-pre\" style=\"background-color:#09090b;color:#cccac2\" tabindex=\"0\" data-language=\"html\" data-theme=\"\"><code data-language=\"html\" data-theme=\"\" style=\"display: grid;\" class=\"md-codeblock\"><span class=\"\" data-line=\"\"><span style=\"color:#5CCFE680\" class=\"\">&#x3C;</span><span style=\"color:#FF6666\" class=\"\">circle</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">  v-for</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"circle in myData\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">  enter-active-class</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"animated fadeIn\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#FFD173\" class=\"\">  leave-active-class</span><span style=\"color:#CCCAC2B3\" class=\"\">=</span><span style=\"color:#D5FF80\" class=\"\">\"animated fadeOut\"</span></span>\n<span class=\"\" data-line=\"\"><span style=\"color:#5CCFE680\" class=\"\">/></span></span><button type=\"button\" title=\"Copy code\" aria-label=\"Copy code\" data=\"<circle\n  v-for=&#x22;circle in myData&#x22;\n  enter-active-class=&#x22;animated fadeIn&#x22;\n  leave-active-class=&#x22;animated fadeOut&#x22;\n/>\" class=\"rehype-pretty-copy\" onclick=\"navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);\"><span class=\"ready\"></span><span class=\"success\"></span></button><style class=\"\">:root {--copy-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E\");--success-icon: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E\");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}</style></code></pre></figure>\n<p class=\"\">It’s also easy enough to use this same pattern with custom-made CSS transitions or animations, but I don’t find myself doing that too often.</p>\n<h3 class=\"\" id=\"database-and-user-authentication\">Database and user authentication</h3>\n<p class=\"\">Over the past 3 months or so, I’ve fallen head over heels for the combination of Vue and Firestore via the <a href=\"https://www.npmjs.com/package/vue-firestore\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://www.npmjs.com/package/vue-firestore\">vue-firestore<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019l-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z\" class=\"\"></path></svg></a> Vue binding. You set up your Firebase app, plug in all your auth keys, and then bind data objects to firestore collections. This binding is instantaneous and reactive, so if your Vue template references an object that is bound to a collection, the page automatically updates if an item is added, removed, or changed in firestore. You don’t have to do anything different. It feels like magic.</p>\n<p class=\"\">So far for me Vue + Firestore has been fast, easy, and scalable. It reminds me of working with Meteor.js way back when.</p>\n<h3 class=\"\" id=\"deploying\">Deploying</h3>\n<p class=\"\">I’ve been really enjoying using Netlify, and the Netlify CLI makes things even easier and faster. Once I’m ready to show someone what I’ve been working on, I just do <code class=\"md-inline-code\">npm run build; netlify deploy —dir=dist</code> and the CLI walks me through creating a URL.</p>\n<p class=\"\">If I end up working on a project for a few days, I’ll go ahead and set up <a href=\"https://www.netlify.com/docs/continuous-deployment/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://www.netlify.com/docs/continuous-deployment/\">Netlify’s continuous deployment<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 512 512\"><path fill=\"currentColor\" d=\"m369.92 176.64l10.24-64l48.64 48.64l-50.892 22.221zm71.68-2.56l51.2 51.2c8.897 9.355 18.922 16.505 19.2 25.6l-126.636-53.814zm67.84 93.44c-21.054 26.636-48.02 50.58-74.24 76.8l-78.053-16.14c0-6.4-4.06-12.136-9.18-15.976l14.273-89.484c7.19.505 15.044-5.542 16.87-11.163zm-88.32 90.88l-96 96l16.64-101.12c4.954-1.686 8.75-4.725 11.52-8.96zM304.64 474.88l-10.24 10.24l-120.32-172.8c1.979-1.59 3.086-3.326 3.84-5.12l133.12 28.16c1.117 6.85 9.24 14.478 14.329 16.64zm-21.76 21.76l-16.64 14.08H243.2c-42.508-42.957-138.24-138.24-138.24-138.24l32.248-49.087c8.487 3.215 18.39 2.608 25.178-1.355zM66.56 334.08l53.76-24.32l3.84 5.12l-33.173 43.333zm-12.8-12.8L6.4 272.64l109.73 22.717zM0 254.72c1.798-13.476 12.956-22.458 19.2-29.44l44.8-44.8l62.72 90.88l-6.544 7.903zm75.52-85.76l60.16-60.16l76.8 33.28c-3.215 11.723.164 21.943 6.4 26.88l-62.447 94.795c-6.313-2.155-10.525-1.156-14.634-.004zm72.96-71.68s90.88-93.44 97.28-96h17.92l39.68 34.56l-55.994 87.149c-9-1.948-20.289-1.296-26.578 4.71zm166.4-49.92l51.2 51.2l-11.8 77.926c-2.808.536-6.63 2.061-8.843 4.483l-80.477-34.357c1.257-5.545.004-11.648-3.84-14.772zm21.76 146.524l-.048 7.848L172.8 271.36l-1.28-1.28l62.293-94.805c12.238 1.132 22.734-4.706 27.648-13.193zM179.2 285.44l163.84-70.4l3.9 3.84l-14.228 88.307c-8.22.982-16.261 6.045-19.876 13.008L181.76 291.84z\" class=\"\"></path></svg></a> with a GitHub repo so that every time I push to master netlify automatically re-builds production from the latest commit.</p>\n<p class=\"\">It doesn’t happen that often, but if I want to go even further, it is super easy to just buy a domain name from <a href=\"https://namecheap.com\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://namecheap.com\">Namecheap</a> and point it at the Netlify DNS servers. Netlify delivers websites incredibly fast, especially <a href=\"https://nuxtjs.org/faq/netlify-deployment/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://nuxtjs.org/faq/netlify-deployment/\">combined with Nuxt<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M21.8 18.36L16.03 8.08c-.1-.08-.4-.65-1-.65c-.25 0-.6.1-.96.65l-.74 1.18l-2.02-3.61c-.05-.1-.4-.65-1-.65c-.25 0-.65.1-.95.65L2.18 18.31c-.05.1-.35.69-.05 1.19c.1.25.4.5 1.06.5h17.66c.1 0 .75 0 1.05-.5c.1-.24.2-.64-.1-1.14m-13.7-.05l-.15.55H3.24l7.12-12.52l2.3 4.13zm1.11.55l4.11-7.2l4.18 7.2zm9.53 0l-.2-.55L14 10.46l1.03-1.73l5.72 10.13z\" class=\"\"></path></svg></a>.</p>\n<p class=\"\">If I need to host really big files that I don’t want to put into a git repo, I’ll put them on S3 and reference the uploaded URLs in the project. I’ve also been experimenting with using <a href=\"https://cloudinary.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://cloudinary.com/\">Cloudinary<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M24 14.86c0 2.173-1.376 3.974-3.59 4.7l-.1.031V17.99c1.378-.58 2.187-1.727 2.187-3.13a3.684 3.684 0 0 0-3.608-3.669h-.596l-.144-.569a6.5 6.5 0 0 0-6.272-4.97a6.39 6.39 0 0 0-5.81 3.664l-.184.376l-.417.044a4.43 4.43 0 0 0-3.305 2.088a4.43 4.43 0 0 0 1.458 6.095v1.69h-.01l-.149-.068a5.94 5.94 0 0 1-3.366-4.35a5.94 5.94 0 0 1 4.8-6.894a7.88 7.88 0 0 1 6.983-4.15a8.03 8.03 0 0 1 7.59 5.589A5.21 5.21 0 0 1 24 14.86M8.682 13.538h.565a.066.066 0 0 0 .046-.111l-2.209-2.211a.066.066 0 0 0-.093 0l-2.211 2.21a.066.066 0 0 0 .048.112h.556a.066.066 0 0 1 .066.065v5.058c0 .659.534 1.193 1.193 1.193h2.604a.066.066 0 0 0 .046-.112l-.33-.33a1.2 1.2 0 0 1-.348-.839v-4.97a.07.07 0 0 1 .067-.065m9.77 2.546h.566a.066.066 0 0 0 .046-.113l-2.21-2.209l-.002-.001a.064.064 0 0 0-.09.001l-2.211 2.209a.066.066 0 0 0 .046.113h.556a.066.066 0 0 1 .066.066v2.51c0 .66.534 1.193 1.193 1.193h2.606a.066.066 0 0 0 .046-.11l-.33-.33a1.2 1.2 0 0 1-.348-.84V16.15a.066.066 0 0 1 .066-.066m-4.885-1.274h.565a.066.066 0 0 0 .047-.111l-2.21-2.203h-.001a.066.066 0 0 0-.093 0l-2.208 2.207a.066.066 0 0 0 .045.111h.563a.07.07 0 0 1 .066.068v3.779c0 .659.534 1.193 1.192 1.193h2.599a.066.066 0 0 0 .047-.112l-.332-.33a1.2 1.2 0 0 1-.346-.839v-3.695c0-.037.03-.067.066-.068\" class=\"\"></path></svg></a> for hosting my images.</p>\n<p class=\"\">From start to finish, I can do everything described in this guide in about 30 minutes.</p>\n<p class=\"\">The lack of friction with this workflow causes me to try and start new things more often. This is really important to me right now as I experiment with different technologies and approaches. I have definitely had ideas and hesitated to start giving them a try because I knew I would need to spin up a new project, set up my IDE, etc. Code that exists is better than a good idea. I’d rather have a dozen half-built prototypes than a hundred good ideas.</p>","title":"How I prototype apps and dataviz quickly","metadata":{"type":"words","hidden":false,"inprogress":false,"date":"2019-03-20T01:25:22.000Z","modified":"2024-09-23T16:13:36.000Z","tags":["vue","dataviz","d3","javascript","webdesign"],"words":932,"images":0,"imageDetails":{"total":0,"cloudinary":0,"withDimensions":0},"links":13,"codeBlocks":3,"headers":{"h2":1},"toc":[{"text":"How I prototype apps and dataviz quickly","slug":"how-i-prototype-apps-and-dataviz-quickly","level":"h2","children":[{"text":"Pulling in data","slug":"pulling-in-data","level":"h3","children":[]},{"text":"Combining Vue and D3 for dataviz","slug":"combining-vue-and-d3-for-dataviz","level":"h3","children":[]},{"text":"Database and user authentication","slug":"database-and-user-authentication","level":"h3","children":[]},{"text":"Deploying","slug":"deploying","level":"h3","children":[]}]}]}},"_links":{"self":"https://ejfox.com/blog/2019/how-i-prototype-apps-and-dataviz-quickly-in-2019.json","html":"https://ejfox.com/blog/2019/how-i-prototype-apps-and-dataviz-quickly-in-2019","index":"/blog.json"}}