I like using hexgrids to summarize spatial statistics. Doing this in R is easy using stat_binhex from ggplot2. There are also client-side binning examples using D3. But what I want are statistics aggregated on the server and simple rendering of those on the client side. To support this use case, I created a Rails model to define the grid and JavaScript to render the results in D3.
To do this efficiently, we need a convention for labeling the bins within the hexgrid layer. We define a base property for the layer which defines the length of the base of the hexagon. Given this base and a grid identifier, we can draw the bin or determine if a point exists within it.
classHexgridLayer<ActiveRecord::Basedeffind_or_create_bin_containing!(x,y)# The origin of the grid system is the left vertex of the base# which has coordinates of (0.5*base, 0) in the rectangle that# circumscribes the hexagon.# That rectangle has width of base*2 and height of base*sqrt(3).# Hexagons in even columns have the same base, while odd columns# have a lower base, in other words, rows are numbered as in# the following diagram:# # / \__/ \__/ \__/ \# \1_/ \1_/ \1_/ \1_/# / \1_/ \1_/ \1_/ \# \0_/ \0_/ \0_/ \0_/# / \0_/ \0_/ \0_/ \#--------------------------# 0 1 2 3 4 5 6 columncol_width=1.5*baserow_offset=0.5*heightcol=(x/col_width).floorx_in_col=x-col*col_width;y+=col%2==1?row_offset:0row=(y/height).floory_in_row=y-row*heightifx_in_col>basedx=x_in_col-basedy=dx*slopeify_in_row<row_offset&&y_in_row<dy# this belongs to the lower right neighborrow-=1ifcol%2==1col+=1elsify_in_row>=row_offset&&y_in_row>=(height-dy)# this belongs to upper right neighborrow+=1ifcol%2==0col+=1endendHexbin.find_or_create_by_layer_id_and_name!(layer_id:self.id,row:row,col:col)endprivatedefheight@height||=base*Math.sqrt(3)enddefslope@slope||=height/baseendend
We define the grid in terms of columns and rows. As the code comments state, the rows actually overlap and we need to distinguish between rows for even columns or rows for odd columns.
Where a row and column intersect, we have three different bins that a point can be placed within: the main bin, the upper-right or the lower-right. Most of the guts of the method has to do with keeping track of even or odd columns and detecting which of the three bins the point belongs in.
In JavaScript, we can define a Hexgrid object that knows how to draw the grid based on row and col.
We just create one grid tile and then use transforms to reuse it for a specific row and column. We could use this in a shading layer like the following.
We would typically only need to add the bins once and would then update the bin shading based on updates from the server. Notice that the record for each bin can have multiple values associated with it. By attaching the record with datum, we can access it in event handlers to recompute the shading based on user inputs. A user would pick a particular statistic from a menu, for example, and we would recompute the class from the attached datum for all the bins. But in our simple example, we just calculate a lib/colorbrewer class based on value.