How to Upload a Picture to Koalas to the Max

1 twenty-four hours I was browsing reddit when I came across this peculiar link posted on information technology: http://www.cesmes.fi/pallo.swf

The game was addictive and I loved information technology only I constitute several pattern elements flawed. Why did it start with four circles and not one? Why was the color separate so jarring? Why was it written in flash? (What is this, 2010?) Nearly importantly, information technology was missing a golden opportunity to dissever into dots that form an paradigm instead of just doing random colors.

Creating the project

This seemed like a fun project, and I reimplemented it (with my pattern tweaks) using D3 to render with SVG.

The main thought was to have the dots split into the pixels of an paradigm, with each bigger dot having the average color of the four dots contained inside of it recursively, and allow the code to piece of work on any web-based image.
The code sat in my 'Projects' folder for some fourth dimension; Valentines day was effectually the corner and I idea information technology could be a cute gift. I bought the domain name, establish a cute picture show, and thus "koalastothemax.com (KttM)" was born.

Implementation

While the user-facing function of KttM has changed picayune since its inception, the implementation has been revisited several times to incorporate bug fixes, improve operation, and bring support to a wider range of devices.

Notable excerpts are presented below and the full lawmaking can be plant on GitHub.

Load the image

If the image is hosted on koalastothemax.com (same) domain so loading it is as unproblematic every bit calling new Image()

var img = new Epitome(); img.onload = function() {  // Awesome rendering code omitted }; img.src = the_image_source;        

I of the core design goals for KttM was to allow people utilize their own images as the revealed paradigm. Thus, when the prototype is on an arbitrary domain, information technology needs to be given special consideration. Given the same origin restrictions, in that location needs to be a image proxy that could channel the image from the arbitrary domain or send the prototype information equally a JSONP call.

Originally I used a library called $.getImageData but I had to switch to a self hosted solution after KttM went viral and brought the $.getImageData App Engine account to its limits.

Extract the pixel data

Once the image loads, information technology needs to be resized to the dimensions of the finest layer of circles (128 x 128) and its pixel information can be extracted with the help of an offscreen HTML5 canvas element.

koala.loadImage = function(imageData) {  // Create a sheet for epitome data resizing and extraction  var canvas = certificate.createElement('canvas').getContext('2d');  // Draw the image into the corner, resizing it to dim 10 dim  sail.drawImage(imageData, 0, 0, dim, dim);  // Extract the pixel information from the aforementioned expanse of sail  // Annotation: This telephone call will throw a security exception if imageData  // was loaded from a different domain than the script.  return sail.getImageData(0, 0, dim, dim).data; };        

dim is the number of smallest circles that will appear on a side. 128 seemed to produce nice results only really any ability of 2 could exist used. Each circle on the finest level corresponds to one pixel of the resized image.

Build the split tree

Resizing the image returns the data needed to render the finest layer of the pixelization. Every successive layer is formed by group neighboring clusters of four dots together and averaging their color. The entire construction is stored as a (quaternary) tree and so that when a circle splits information technology has easy access to the dots from which it was formed. During construction each subsequent layer of the tree is stored in an efficient 2nd array.

// Got the information at present build the tree var finestLayer = array2d(dim, dim); var size = minSize;  // Start off by populating the base of operations (leafage) layer var 11, yi, t = 0, color; for (yi = 0; yi < dim; yi++) {  for (xi = 0; 11 < dim; xi++) {    color = [colorData[t], colorData[t+1], colorData[t+2]];    finestLayer(11, yi, new Circumvolve(vis, xi, yi, size, color));    t += four;  } }        

Start by going through the color data extracted in from the image and creating the finest circles.

// Build upward successive nodes by grouping var layer, prevLayer = finestLayer; var c1, c2, c3, c4, currentLayer = 0; while (size < maxSize) {  dim /= 2;  size = size * ii;  layer = array2d(dim, dim);  for (yi = 0; yi < dim; yi++) {    for (xi = 0; xi < dim; 11++) {      c1 = prevLayer(2 * xi    , 2 * yi    );      c2 = prevLayer(2 * 11 + 1, 2 * yi    );      c3 = prevLayer(2 * 11    , 2 * yi + 1);      c4 = prevLayer(2 * xi + 1, ii * yi + i);      color = avgColor(c1.color, c2.color, c3.color, c4.colour);      c1.parent = c2.parent = c3.parent = c4.parent = layer(xi, yi,        new Circle(vis, xi, yi, size, color, [c1, c2, c3, c4], currentLayer, onSplit)      );    }  }  splitableByLayer.push(dim * dim);  splitableTotal += dim * dim;  currentLayer++;  prevLayer = layer; }        

After the finest circles have been created, the subsequent circles are each congenital past merging four dots and doubling the radius of the resulting dot.

Render the circles

Once the dissever tree is built, the initial circle is added to the folio.

// Create the initial circle Circle.addToVis(vis, [layer(0, 0)], true);        

This employs the Circle.addToVis office that is used whenever the circumvolve is split. The 2d argument is the assortment of circles to exist added to the page.

Circle.addToVis = function(vis, circles, init) {  var circle = vis.selectAll('.nope').data(circles)    .enter().append('circumvolve');   if (init) {    // Setup the initial country of the initial circle    circle = circle      .attr('cx',   function(d) { return d.ten; })      .attr('cy',   function(d) { return d.y; })      .attr('r', four)      .attr('fill up', '#ffffff')        .transition()        .duration(1000);  } else {    // Setup the initial land of the opened circles    circle = circle      .attr('cx',   part(d) { return d.parent.x; })      .attr('cy',   role(d) { return d.parent.y; })      .attr('r',    function(d) { return d.parent.size / two; })      .attr('fill', part(d) { return String(d.parent.rgb); })      .attr('fill-opacity', 0.68)        .transition()        .duration(300);  }   // Transition the to the respective last country  circumvolve    .attr('cx',   function(d) { return d.10; })    .attr('cy',   office(d) { render d.y; })    .attr('r',    function(d) { return d.size / 2; })    .attr('fill up', function(d) { return Cord(d.rgb); })    .attr('make full-opacity', 1)    .each('cease',  office(d) { d.node = this; }); }        

Here the D3 magic happens. The circles in circles are added (.append('circle')) to the SVG container and animated to their position. The initial circle is given special treatment as it fades in from the eye of the page while the others slide over from the position of their "parent" circle.

In typical D3 fashion circle ends up being a selection of all the circles that were added. The .attr calls are applied to all of the elements in the selection. When a role is passed in it shows how to map the split tree node onto an SVG element.

.attr('cx', function(d) { render d.parent.x; }) would gear up the X coordinate of the eye of the circle to the 10 position of the parent.

The attributes are gear up to their initial country and so a transition is started with .transition() and and then the attributes are set to their final state; D3 takes intendance of the blitheness.

Discover mouse (and touch) over

The circles need to split when the user moves the mouse (or finger) over them; to be done efficiently the regular structure of the layout can be taken reward of.

The described algorithm vastly outperforms native "onmouseover" effect handlers.

// Handle mouse events var prevMousePosition = null; function onMouseMove() {  var mousePosition = d3.mouse(vis.node());   // Do nothing if the mouse point is not valid  if (isNaN(mousePosition[0])) {    prevMousePosition = null;    return;  }   if (prevMousePosition) {    findAndSplit(prevMousePosition, mousePosition);  }  prevMousePosition = mousePosition;  d3.effect.preventDefault(); }  // Initialize interaction d3.select(certificate.body)  .on('mousemove.koala', onMouseMove)        

Firstly a body broad mousemove upshot handler is registered. The event handler keeps track of the previous mouse position and calls on the findAndSplit function passing information technology the line segments traveled past the user's mouse.

function findAndSplit(startPoint, endPoint) {  var breaks = breakInterval(startPoint, endPoint, 4);  var circleToSplit = []   for (var i = 0; i < breaks.length - 1; i++) {    var sp = breaks[i],        ep = breaks[i+1];     var circumvolve = splitableCircleAt(ep);    if (circle && circle.isSplitable() && circle.checkIntersection(sp, ep)) {      circle.split up();    }  } }        

The findAndSplit function splits a potentially large segment traveled by the mouse into a series of small segments (not bigger than 4px long). It then checks each small segment for a potential circle intersection.

office splitableCircleAt(pos) {  var xi = Math.floor(pos[0] / minSize),      yi = Math.floor(pos[1] / minSize),      circle = finestLayer(xi, yi);  if (!circle) return aught;  while (circle && !circumvolve.isSplitable()) circumvolve = circle.parent;  return circle || nada; }        

The splitableCircleAt role takes advantage of the regular structure of the layout to detect the one circle that the segment ending in the given point might be intersecting. This is washed by finding the leaf node of the closest fine circle and traversing up the carve up tree to find its visible parent.

Finally the intersected circle is dissever (circle.split()).

Circle.prototype.split = role() {  if (!this.isSplitable()) return;  d3.select(this.node).remove();  delete this.node;  Circle.addToVis(this.vis, this.children);  this.onSplit(this); }        

Going viral

Former subsequently Valentines 24-hour interval I run into with Mike Bostock (the creator of D3) regarding D3 syntax and I showed him KttM, which he thought was tweet-worthy - it was, after all, an early example of a pointless artsy visualization done with D3.

Mike has a twitter following and his tweet, which was retweeted by some members of the Google Chrome evolution team, started getting some momentum.

Since the koala was out of the bag, I decided that it might besides be posted on reddit. I posted it on the programing subreddit with the tile "A cute D3 / SVG powered image puzzle. [No IE]" and it got a respectable 23 points which made me happy. Afterwards that twenty-four hour period it was reposted to the funny subreddit with the championship "Press all the dots :D" and was upvoted to the front end page.

The traffic went exponential. Reddit was a fasten that quickly dropped off, but people have picked up on information technology and spread information technology to Facebook, StumbleUpon, and other social media outlets.

The traffic from these sources decays over fourth dimension but every several months KttM gets rediscovered and traffic spikes.

Such irregular traffic patterns underscore the need to write scalable code. Conveniently KttM does most of the work inside the user'south browser; the server needs only to serve the page assets and ane (minor) paradigm per page load allowing KttM to be hosted on a clay-cheap shared hosting service.

Measuring engagement

After KttM became popular I was interested in exploring how people actually interacted with the application. Did they even realize that the initial single circle can split? Does anyone really finish the whole image? Do people uncover the circles uniformly?

At first the only tracking on KttM was the vanilla GA code that tracks pageviews. This apace became underwhelming. I decided to add together custom issue tracking for when an entire layer was cleared and when a percentage of circles were split (in increments of five%). The result value is set up to the time in seconds since page load.

Every bit you lot can see such issue tracking offers both insights and room for improvement. The 0% clear event is fired when the get-go circle is split and the average time for that event to fire seems to be 308 seconds (5 minutes) which does non audio reasonable. In reality this happens when someone opens KttM and leaves it open for days and so, if a circle is split up, the event value would be huge and it would skew the boilerplate. I wish GA had a histogram view.

Even basic engagement tracking sheds vast amounts of light into how far people get through the game. These metrics proved very useful when the the mouse-over algorithm was upgraded. I could, afterwards several days of running the new algorithm, see that people were finishing more of the puzzle earlier giving up.

Lessons learned

While making, maintaining, and running KttM I learned several lessons about using mod web standards to build web applications that run on a broad range of devices.

Some native browser utilities give y'all 90% of what you demand, but to get your app behaving exactly equally you want, you need to reimplement them in JavaScript. For case, the SVG mouseover events could non cope well with the number of circles and information technology was much more efficient to implement them in JavaScript by taking reward of the regular circle layout. Similarly, the native base64 functions (atob, btoa) are not universally supported and do non work with unicode. It is surprisingly easy to support the modernistic Internet Explorers (9 and 10) and for the older IEs Google Chrome Frame provides a great fallback.

Despite the huge improvements in standard compliance it is still necessary to test the code on a wide variety of browsers and devices, as in that location are nonetheless differences in how certain features are implemented. For example, in IE10 running on the Microsoft Surface html {-ms-touch-action: none; } needed to be added to let KttM to function correctly.

Calculation tracking and taking time to define and collect the central engagement metrics allows y'all to evaluate the impact of changes that become deployed to users in a quantitative mode. Having well divers metrics allows you to run controlled tests to figure out how to streamline your application.

Finally, heed to your users! They option upward on things that you miss - even if they don't know information technology. The congratulations message that appears on completion was added after I received complaints that is was non clear when a picture was fully uncovered.

All projects are forever evolving and if you listen to your users and run controlled experiments then there is no limit to how much you tin improve.

Vadim is a developer at Metamarkets where he uses D3 to build interactive data-driven applications on top of modern web technologies. Prior to working at Metamarkets, Vadim was part of the Stanford Data Visualization grouping where he contributed to Protovis and other open up-source data visualization projects. His open-source development is now focused on DVL, a reactive data catamenia library for dynamic data visualization built on top of D3.

More manufactures by Vadim Ogievetsky…

mooreandescols.blogspot.com

Source: https://hacks.mozilla.org/2013/01/koalas-to-the-max-a-case-study/

0 Response to "How to Upload a Picture to Koalas to the Max"

إرسال تعليق

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel