Flot won't Plot (Ruby) - ruby-on-rails

I'm playing around with FLOT with Ruby and I'm having a hard time passing the code to javascript; I know my javascript isn't reading the data correctly from Ruby. I need some syntax help.
o={};
o= {'label' => "A", 'data' => #example.collect{|x| [Time.utc(x.date).to_i, x.num]}}
render :text => o.to_json
I have successfully rendered the output as such:
{"label":"A","data":[[1281225600,1.31],[1281225600,1.31],[1281225600,1.25]]}
The HTML outputs this data only.
My javascript is as follows:
var obj = jQuery.parseJSON(text);
var options = {
lines: { show: true },
points: { show: true },
xaxis: { mode: "time", timeformat: "%m/%d/%y", minTickSize: [1, "day"]}
};
var data = obj;
$.plot(placeholder,[data],options);
}

What you are missing is that Ruby puts out timestamps in the Unix format (i.e. seconds since the epoch 1281225600). Flot requires javascript timestamps, which count the milliseconds since the epoch, i.e. 1281225600*1000.
So in your Ruby code, your best bet is to do something like this:
o={};
o= {'label' => "A", 'data' => #example.collect{|x| [Time.utc(x.date).to_i*1000, x.num]}}
render :text => o.to_json
Or if you prefer, you could loop over the obj.data and do the multiplication on the Javascript side:
for (i=0;i<obj.data.length;i++){
obj.data[i] = obj.data[i]*1000;
}

Ok I got it....
Ruby code:
#o={};
#o= {'label' => "A", 'data' => #example.collect{|x| [Time.utc(x.date).to_i, x.num]}}
Java code:
$(function () {
var obj = <%= #o.to_json %>;
var options = {
lines: { show: true },
points: { show: true },
xaxis: { mode: "time", timeformat: "%m/%d/%y", minTickSize: [1, "day"]}
};
var data = obj;
$.plot(placeholder,[data],options);
});

Related

Is there a specific way to add data points from fetch() after a chart has been built?

I have data which I fetch() and add to a time series chart once they are available. The received data are in the format
[
{
"count": 1932,
"date": "2018-03-29T00:00:00"
},
{
"count": 3957,
"date": "2018-03-28T00:00:00"
},
{
"count": 3864,
"date": "2018-03-27T00:00:00"
},
]
and I use addPoint() to update the chart.
The chart is correctly updated with the Y ("count") but the X axis is ticked by seconds.
This would suggest a wrong format of the time data. However, the following code (which builds the chart)
let chart = Highcharts.chart("app", {
xAxis: {
type: "datetime"
},
series: [
{
name: "candidates",
data: [],
},
{
name: "scanned",
data: [],
},
],
})
// get candidates
fetch("https://example.com/rpc/candidat_per_day", {
method: "POST",
body: JSON.stringify({
"limit_days": 15
}),
headers: new Headers({
'content-type': 'application/json'
}),
})
.then(r => r.json())
.then(r => {
r.reverse().pop()
r.forEach(data => {
console.log(moment(data.date).format("x"))
chart.series[0].addPoint([moment(data.date).format("x"), data.count])
})
})
fetch("https://example.com/rpc/scanned_per_day", {
method: "POST",
body: JSON.stringify({
limit_days: 15,
}),
headers: new Headers({
"content-type": "application/json",
}),
})
.then(r => r.json())
.then(r => {
r.reverse().pop()
r.forEach(data => {
d = moment(data.date).format("x")
console.log(d)
chart.series[1].addPoint([d, data.count])
})
})
outputs on the console numbers such as 1521414000000 or 1521500400000. When converting them independently I get respectively the 18 and 19 of March 2018 - the expected date for these data with the confirmation that they are in ms.
Worse: when trying to replicate the issue by adding these points to an existing chart, everything is OK:
let chart = Highcharts.chart('container', {
xAxis: {
type: 'datetime'
},
series: [{
data: []
}]
});
chart.series[0].addPoint([1519772400000, 5])
chart.series[0].addPoint([1520895600000, 6])
chart.series[0].addPoint([1521241200000, 4])
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container"></div>
The difference I see between the replication code and the one I actaully run is the fetch() part (and the promises handling) but I do not see any reasons for the code not to work that way.
I believe there is a minor issue somewhere (probably around the time handling), but after staring at the code for hours I cannot find anything wrong. What should I try next?
The problem here is that format() function returns a String:
// in console:
moment().format("x")
"1522407551509"
Highcharts expect the x coordinate to be a Number.
You can use parseInt() for the conversion:
console.log(parseInt(moment().format("x")))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.min.js"></script>

Loop through array of hashes in rails

I have the following array of hashes that I send to my controller:
comparisons = [{
"startdate": "01/01/2016",
"enddate": "01/02/2016"
}, {
"startdate": "01/03/2016",
"enddate": "01/04/2016"
}, {
"startdate": "01/05/2016",
"enddate": "01/06/2016"
}, {
"startdate": "01/05/2016",
"enddate": "01/06/2016"
}];
$.ajax({
url: '/get_data',
method: 'GET',
dataType: 'JSON',
data: {
startdate: '01/01/2016',
enddate: '01/01/2016',
comparisons: comparisons
},
success: function (data) {
console.log(data);
}
});
And then in the controller I want to loop through these comparisons:
#comparisons = []
if !params[:comparisons].blank?
params[:comparisons].each do |c|
#comparisons.push({:startdate => c[:startdate], :enddate => c[:enddate], :data => get_data(c[:startdate], c[:enddate], 'week')})
end
end
I get an error: > no implicit conversion of Symbol into Integer
And when debugging I'm finding that the c in my loop isn't structured the same as what I'm sending...
c: ["0", {"startdate"=>"01/01/2016", "enddate"=>"01/01/2016"}]
How can I fix this?
easiest fix would be to change how you refer to the c data... you can see that c is an array (explains the error, it's expecting array elements to be accessed by an integer) and you can see the second (last) element of the c array has the data you want.
#comparisons.push({:startdate => c.last[:startdate],
:enddate => c.last[:enddate],
:data => get_data(c.last[:startdate], c.last[:enddate], 'week')})

Represent Stripe Charge Amount and Date with Chart.js

My code comes from this and Im displaying the data with chart.js inside of a React component. Im using Rails 4. In the controller, the charge props comes out as: #charges = charges_by_date = {}.as_json
jsx:
var ctx = document.getElementById("moneyHistory");
var hash = this.props.charges;
var amount = [];
var d = [];
var chargeDate = Object.keys(hash).forEach(function(k) {
var cost = hash[k];
var date = moment(k).format("Do MMM");
amount.push(cost);
d.push(date);
});
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: d.sort(),
datasets: [{
label: 'Amount',
data: amount.sort(),
backgroundColor: 'rgba(0, 126, 255, 0.72)',
borderColor: '#1b6ac9',
width: 100
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
},
render: function() {
return(
<div>
<canvas id="moneyHistory" width="400" height="150"></canvas>
</div>
)
}
Chart displays great but I want to have 7 days on the graph (today + 6 days ago). I want to replace the hash dates with:
var staticDates = [
moment().subtract(6, 'days').format("Do MMM"),
moment().subtract(5, 'days').format("Do MMM"),
moment().subtract(4, 'days').format("Do MMM"),
moment().subtract(3, 'days').format("Do MMM"),
moment().subtract(2, 'days').format("Do MMM"),
moment().subtract(1, 'days').format("Do MMM"),
moment().subtract(0, 'days').format("Do MMM")
];
...
data: {
labels: staticDates,
...
Now, I need to associate those charge with those date. How to achieve this?
Edit:
I'd like this dates on the graph to be like this where today's date (30th Apr) always be on the right:
I don't think you want to do all this date processing in Javascript. Let Ruby/Rails do the heavy lifting for you.
Suppose you have a method that maps charges to a date, where each key in the hash is a day and the value is a count:
def each_stripe_charge_for_customer(customer_id, created)
starting_after = nil
loop do
customer_charges = Stripe::Charge.all customer: customer_id,
created: created,
limit: 100,
starting_after: starting_after
break if customer_charges.none?
customer_charges.each do |charge|
yield charge
end
starting_after = customer_charges.data.last.id
end
end
def daily_charges_for_customer(customer_id, created=nil)
charges_by_date = Hash.new(0)
# For each Stripe charge, store the date and amount into a hash.
each_stripe_charge_for_customer(customer_id, created) do |stripe_charge|
# Parses Stripe's timestamp to a Ruby date object. `to_date` converts a DateTime object to a date (daily resolution).
charge_date = Time.at(stripe_charge.created).to_date
charge_amount = stripe_charge.amount
charges_by_date[charge_date] += charge_amount
end
charges_by_date
end
So now, daily_charges_for_customer(customer_id, 1.week.ago.to_i) returns something like:
{
'19 Jan' => 10,
'20 Jan' => 20
}
You should cache this result whenever possible so you're not constantly hitting the Stripe API
Now, you can pass this as a data-attribute within the DOM:
In your view:
<%= content_tag :canvas, "", id: "myChart", width: "400", height: "200", data: { charges: daily_charges_for_customer(customer_id, 1.week.ago.to_i) } %>
This will simplify your javascript considerably:
In your JS code:
var ctx = $("#moneyHistory");
var cumulative_daily_charges = ctx.data("charges");
var date_labels = Object.keys(cumulative_daily_charges);
var daily_charges = date_labels.map(function(v) { return cumulative_daily_charges[v]; });
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: date_labels,
datasets: [{
label: 'Date',
data: daily_charges
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
I'm writing this sort of "off the cuff" so you may need to fix some typos/syntax errors, but the logic and major nuts-and-bolts should work. Let me know if you run into any issues.
EDIT
Also, I don't think amount.sort() and d.sort() will act as you expect them to. If they're separate arrays, they'll be sorted independently.

How to initialize the selection for rails-select2 in BackboneForms schema?

The project uses marionette-rails, backbone-on-rails, select2-rails and this port to BackboneForms to provide a multiselect form field. The select options are available to the user. They are retrieved from the collection containing the total list of options:
MyApp.module("Products", function(Products, App, Backbone, Marionette, $, _) {
Products.CustomFormView = Products.CustomView.extend({
initialize: function(options) {
this.model.set("type", "Product");
Products.EntryView.prototype.initialize.apply(this, arguments);
},
schemata: function() {
var products = this.collection.byType("Product");
var productTypes = products.map(function(product){
return {
val: product.id,
label: product.get("name")
};
});
return {
productBasics: {
name: {
type: "Text",
title: "Name",
editorAttrs: {
maxLength: 60,
}
},
type: {
type: 'Select2',
title: "Product type",
options: {
values: productTypes,
value: [3, 5],
initSelection: function (element, callback) {
var data = [];
$(element.val().split(",")).each(function () {
data.push({id: this, text: this});
});
callback(data);
}
},
editorAttrs: {
'multiple': 'multiple'
}
}
}
};
}
});
});
Do I initialize the value correctly in options.value? How comes initSelection is never called? I copied the function from the documentation - it might be incomplete for my case. None of the products with the IDs 3 and 5 is displayed as the selection.
initSelection is only used when data is loaded asynchronously. My understanding is that there is no way of specifying the selection upon initialization if you are using an array as the data source for a Select2 control.
The best way of initializing the selection is by using setValue after the form is created. Here is a simplified example based on the code in your example.
var ProductForm = Backbone.Form.extend({
schema: {
type: {
type: 'Select2',
title: "Product type",
options: {
values: productTypes,
},
editorAttrs: {
'multiple': 'multiple'
}
}
});
var form = new ProductForm({
model: new Product()
}).render();
form.setValue("type", [3, 5]);
You can use value function (http://ivaynberg.github.io/select2/#documentation) in setValue. I personally recomend you to use this backbonme-forms plugin: https://gist.github.com/powmedia/5161061
There is a thread about custom editors: https://github.com/powmedia/backbone-forms/issues/144

setData and setCategory highcharts from rails

I'm using highcharts 3.0.1 with ruby on rails.
basically i have finally this javascript ( in an ajax/remote request) :
var chart = $('#my_bar_div').highcharts();
chart.xAxis[0].setCategories(["cat1", "cat2", "cat4"]);
chart.series[0].setData([6.0,7.0,8.0]);
ruby version _updatechart.js.erb:
var chart = $('#my_bar_div').highcharts();
chart.xAxis[0].setCategories(<%= #qids.to_s.html_safe %>);
chart.series[0].setData(<%= #average_values.to_json %>);
i'm going back from 6 to 3 categories/rows, I use a vertical column chart:
#bar = LazyHighCharts::HighChart.new('column') do |f|
f.series(:name=>'Punkte',:data=> #average_values )
f.title({ :text=>"Auswertung Fragen " + #theobjects.name })
### Options for Bar
f.options[:chart][:defaultSeriesType] = "bar"
f.plot_options({:series=>{:stacking=>"normal"}, :bar => { :dataLabels => { :enabled => true }}})
f.options[:xAxis] = {:plot_bands => "none", :title=>{:text=>""}, :categories => #qids}
f.options[:yAxis] = {:title => { :text =>'Punkte', :align =>'high'}, :labels => { :overflow => 'justify' } }
end
it resizes the categories, but does not redraw the columns, I tried almost everything without success.
Maybe you got any ideas?
Thanks, Patrick
Fixed example: http://jsfiddle.net/BCRyP/4/
1) "plotBands": "none" -> remove that or use {}
2) Use the same variable's name:
var chart;
$(document).ready(function () {
var chart = new Highcharts.Chart(options);
jQuery("#button").on("click", function () {
chart.xAxis[0].setCategories(["Wie geht es?", "How is your mam?"], true);
chart.series[0].setData([5.5, 6.0], true);
});
});

Resources