Vue responsive reports

Torsten Ruger
rubydesign_web
Published in
4 min readFeb 20, 2018

This is a short write up of a responsive report engine. Client side Vue, server rails, though it’s json, so could be anything. I’ve previously described the setup, and even without going into that, it’ll mostly describing the general approach, rather than going though the 300 lines of code.

The Report View

Video in previous post

The resulting view, captured here. I’ve made a video, shown in the first post. (Medium doesn’t create an overview pic from the video, hence the picture).

As you would expect from a report, you can choose the time frame (top right) and the you can filter by different criteria. This shows sales, so we can filter by Supplier and Category (button, top row). We can also choose to display just a total, or like in the picture, several lines, here representing a category each.

The Data

Now that we know what it does, i’ll try to explain how. First, off course, we need data. We can add the data to the rendered view, as a vue data instance variable, just like we did in the table (last post). This gives us a quick start, but off course we need to make an ajax call, but only when the time frame changes. This is easily achieved by a click handler:

%a{ “@click” => “minusYear” }
=image_tag “minus.png” , width: “25px”
def minusYear(_)
@start.subtract(50,"week").startOf("month")
@end.subtract(54,"week").endOf("month")
this.loadData()
end

Where we get the data with the loadData, which will upon return just change out the data we are using. The same Vue data instance we started with, and because Vue is so great, that will just change everything else as needed.

def loadData(_)
@start = moment(@start)
@end = moment(@end)
jQuery.getJSON(“/manage/reports.json” ,
{ start: @start.unix() , end: @end.unix() }) do
|ret| @items = ret
end
end

It really is almost too easy. The only thing to think about here, is the structure of the data and hence how to serve it up. The actual server function is a screen, so i’ll just show the heart of it:

@items = Item.where(created_at: @start..@end).
includes(:product).includes(:basket).
where.not(baskets: {locked: nil}).
pluck(*@attributes).map do|item|
item[created_index] = item[created_index].to_i*1000
item[price_index] = item[price_index]*item[quantity_index]
item << emails[ item[basket_index] ]
item
end

The method returns all kinds of config as well (for inital render) but the @items is the heart of the matter. As you can see a simple array (no structure) which is we just to_json and serve. Customer, category and supplier data is folded in, and with a little effort a very efficient (think ms) query is created.

Filtering, grouping and slicing

The filtering part is really quite similar to the previous data-table example. Basically we create a function filteredData, that filters the data according to which category or supplier is selected. The method depends on the category and supplier variables, so the click handlers for the buttons only need to change the variable and Vue will take care of the rest (great!). The whole code is here, btw.

Grouping and slicing the data sort of go hand in hand, basically what we need is a timeline. The timescale depends on the time (start-end) and the interval chosen, which are all Vue data variables. We create an empty series as a hash of dates vs 0 values. The we group the data according to the chosen grouping, which is yet another hash, group label vs data array.

Then we go though the groups and create a series for each. By going through the groups data and adding to the appropriate buckets, we get basically a matrix of data. Each row of the matrix is a line in the graph and the key of is the label.

Chartist.js

Now that we have sliced and grouped the data correctly we are ready to display it. Nowadays one is quite spoiled for choice when it comes to graph libraries. But i found one stands out in terms of simplicity and clean approach, namely Chartist.js. The reason i chose Chartists is that is renders the data to svg. I will make another post on the benefits of using svg, but it boils down to letting the browser do the work. Which in turn means that Chartist is something like 10k.

Now we could (quite easily) drive Chartist straight from our app, but Yoann Bourde has made a small component, so i used that. It mostly just calls chartist render function with the data that we pass in.

Done

So basically we’re done. Whenever anything changes (from the user interface), Vue takes care of updating whatever needs to be updated and the component calls the update-chart to re-render our graph.

In summary, it is quite similar to the data table in that we pass initial data in on the render, use a computed property to update the rendered data when filters change and let vue handle the dependencies. Additionally we load data dynamically when the time scale changes, and instead of rendering html, we use a library to render svg. The only complication comes from the fact that the data is time based and we have to create the displayed matrix.

The code that creates the matrix is remarkably similar to a previous server side version. But since the computation happens in the browser we save the network trip and the user get a much more responsive experience.

--

--