Line not rendered with data in variable but rendering with array data - ruby-on-rails

I have a method which renders a line graph with Highcharts. The data in the graph is an array in the avg_rates variable, which has these values:
[0.8936e2, 0.8901e2, 0.9015e2, 0.9043e2, 0.8994e2, 0.9105e2, 0, 0, 0, 0, 0, 0]
When I use the variable it doesn't render the line:
f.series(
name: "Average rates",
data: avg_rates
)
When I use the array it does render the line:
f.series(
name: "Average rates",
data: [0.8936e2, 0.8901e2, 0.9015e2, 0.9043e2, 0.8994e2, 0.9105e2, 0, 0, 0, 0, 0, 0]
)
I have no ideay why this is happening? Here is the method in full:
def create_avg_rates_chart(avg_rates)
months = ['jan','feb','mar','apr','mei','jun','jul','aug','sep','okt','nov', 'dec']
min = avg_rates[0] - 5
max = avg_rates[0] + 5
avg_rates_chart = LazyHighCharts::HighChart.new('graph') do |f|
f.chart(type: 'line', height: '150', width: '1000')
f.pane(size: '100%')
f.colors(['#0092C9'])
f.xAxis(
categories: months.map{ |m| [m] },
labels: {
style: { "fontSize": "12px" }
}
)
f.yAxis(
title: {
text: 0
},
min: min,
max: max
)
f.plotOptions(
line: {
dataLabels: {
enabled: true,
padding: 10
},
enableMouseTracking: false,
lineWidth: 4,
marker: {
radius: 5
}
}
)
f.series(
name: "Average rates",
data: avg_rates
)
f.legend(enabled: false)
end
end

This most likely happens because when you copied the contents from the array variable you don't copy the actual object but just the console representation (aka the result of #inspect).
The array avg_rates most likely happens to contain BigDecimals:
# avg_rates comes in from outside the function, the line below is a mock.
avg_rates = %w[89.36 89.01 90.15 90.43 89.94 91.05 0 0 0 0 0 0].map(&:to_d)
These values are represented in the console using the #inspect method and produce the following visual output:
#=> [0.8936e2, 0.8901e2, 0.9015e2, 0.9043e2, 0.8994e2, 0.9105e2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
This visual output is also perfectly valid Ruby code. However if you type 0.8936e2 into the console you'll instantiate a Float and not a BigDecimal. This can be seen if you look at the return value of just entering the the representation of the array:
avg_rates_repr = [0.8936e2, 0.8901e2, 0.9015e2, 0.9043e2, 0.8994e2, 0.9105e2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
#=> [89.36, 89.01, 90.15, 90.43, 89.94, 91.05, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
avg_rates.map(&:class).uniq
#=> [BigDecimal]
avg_rates_repr.map(&:class).uniq
#=> [Float]
As you said the graph doesn't render when using the avg_rates variable, but does with the copying the representation. This means that LazyHighCharts::HighChart doesn't accept BigDecimals but does accept Floats. So your solution should be to convert the values to Floats before passing them to the :data option of #series.
data: avg_rates.map(&:to_f)

Related

How to make legend color show up in Highcharts for a line graph under certain conditions?

I'm exploring Highcharts and it seems like a very comprehensive package! I have a question regarding legend colors.
I notice that when I use a linear color gradient for a line, the color next to the legend (I believe it's called the symbolColor) may or may not show up, depending on the format I use:
1) symbolColor works fine: linearGradient: [ 00, 00, 00, 350 ]
2) symbolColor doesn't show up: linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }
I'd rather use the second format; it's easier and the first is not responsive.
Should the symbolColor always show up and this is an obscure issue/bug? Or am I doing something wrong? I also just noticed that if the marker is enabled in the plotOptions for series, symbolColor works fine with the second format. But I have many data points and don't want to use markers.
The fiddle graph has two lines -- purple and green -- and while the purple symbolColor using method (1) looks fine, you'll notice that the green symbolColor using method (2) doesn't show up.
https://jsfiddle.net/jwinkle/s6d9ah17/6/
In your HTML file, add:
<script src="https://code.highcharts.com/maps/modules/map.src.js"></script>
Above your chart code add:
Highcharts.addEvent(Highcharts.Chart.prototype, 'render', function() {
this.series.forEach(function(series) {
if (series.legendLine) {
var pathDef = series.legendLine.attr('d').split(' ');
pathDef[5] = parseFloat(pathDef[2]) + 0.0001;
series.legendLine.attr({
d: pathDef
});
}
})
});
The fix is in adding a smidge to your path definition (you can test this by going into the svg path element in dev tools and changing the last number in the 'd' attribute to a float:
<path fill="none" d="M 0 11 L 16 11" ... ></path>
becomes:
<path fill="none" d="M 0 11 L 16 11.1" ... ></path>
The function above goes through each series and massages the line data adding the fractional value in the right place. Total hack. However why the line does not appear if it does have this fractional value, I don't know. I would also test this across browsers to see that it works consistently as it may not (I tested in Chrome).
Here is a somewhat related issue where I cribbed the fix: https://www.highcharts.com/forum/viewtopic.php?t=38588
(Most of it is addressing a completely different problem, but the legend lines did show up so I investigated why).
If you don't want to use the extra library, you could probably write a native js function to find the legend lines and manipulate them in the same way.
Here, you can find a nice explanation of the problem.
The difference results from the fact that Highcharts uses 'userSpaceOnUse' as a default value for gradientUnits if a gradient is defined as an array.
Highcharts source code:
// Keep < 2.2 kompatibility
if (isArray(gradAttr)) {
(colorOptions as any)[gradName] = gradAttr = {
x1: gradAttr[0] as number,
y1: gradAttr[1] as number,
x2: gradAttr[2] as number,
y2: gradAttr[3] as number,
gradientUnits: 'userSpaceOnUse'
};
}
As a possible solution, you can set gradientUnits also for linearGradient defined as an object:
color: {
linearGradient: {
...,
gradientUnits: 'userSpaceOnUse'
},
...
}
Live demo: http://jsfiddle.net/BlackLabel/yz6sagxj/
Or modify the paths very slightly so that they are not perfectly horizontal
chart: {
events: {
render: function() {
this.series.forEach(function(series) {
if (series.legendLine) {
var pathDef = series.legendLine.attr('d').split(' ');
pathDef[5] = parseFloat(pathDef[2]) + 0.0001;
series.legendLine.attr({
d: pathDef
});
}
})
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/L1kzg0cn/
Github thread: https://github.com/highcharts/highcharts/issues/1936
In addition to #Katharine's solution, here's another I discovered.
If I enable the marker in one series (setting the radius to 0) and then put the data in a second series which is linked to the previous, it works. (If I do it all in one series, there is again no legend color.)
series: [{
name: 'purple', lineWidth: 3, color: {
linearGradient: [ 00, 00, 00, 350 ],
//linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1, },
stops: [
[0, 'purple'], // start
[1, 'black'] // end
],
},
data: [ 100, 95, 80, 60, 35, 50, 20, 10, 3, 2, 30, 40, ],
},{
marker: {
enabled: true,
radius: 0,
},
name: 'green', color: 'green', lineWidth: 3,
},{
linkedTo: ':previous',
lineWidth: 3, color: {
//linearGradient: [ 00, 00, 00, 350 ],
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1, },
stops: [
[0, 'green'], // start
[1, 'black'] // end
],
},
data: [ 100, 100, 95, 80, 60, 35, 50, 20, 10, 3, 2, 30, ],
}],
Here's the "fixed" fiddle: https://jsfiddle.net/jwinkle/wztq78n1/6/
I reported the original issue as a possible bug to Highcharts, and they confirmed that it's an SVG rendering problem out of their control.

Dart group by iterable

New to dart, with origin from python. One thing I cannot find native support for is to be able to group an Iterable by a defined key. In python there is a very nice support from the itertools package (https://docs.python.org/3.8/library/itertools.html#itertools.groupby). What I want to achieve can be described in pseudo code.
a = [
{
x: 1,
y: 1,
},
{
x: 1,
y: 2,
},
{
x: 2,
y: 3,
},
{
x: 2,
y: 4,
}
]
for group, items in groupby(a, key: item['x']):
print('$group : $items')
Expected output:
1 : {x:1, y: 1}, {x:1, y:2}
2 : {x:2, y: 3}, {x:2, y:4}

Show Series and colorAxis both in Legend

Is it possible to have both colorAxis and series in the legend? http://jsfiddle.net/6k17dojn/ i see i can only show one at a time when I toggle this setting
colorAxis: {
showInLegend: true,
}
Currently to show a basic legend with colorAxis, you need to add some code to Highcharts core. This plugin below allows you to add colorAxis to a legend if showInLegend property is set to false:
(function(H) {
H.addEvent(H.Legend, 'afterGetAllItems', function(e) {
var colorAxisItems = [],
colorAxis = this.chart.colorAxis[0],
i;
if (colorAxis && colorAxis.options) {
if (colorAxis.options.dataClasses) {
colorAxisItems = colorAxis.getDataClassLegendSymbols();
} else {
colorAxisItems.push(colorAxis);
}
}
i = colorAxisItems.length;
while (i--) {
e.allItems.unshift(colorAxisItems[i]);
}
});
}(Highcharts))
Live demo: http://jsfiddle.net/BlackLabel/hs1zeruy/
API Reference: https://api.highcharts.com/highcharts/colorAxis.showInLegend
Docs: https://www.highcharts.com/docs/extending-highcharts
It's possible, but not with the data you currently work with. A heatmap's data is a set of coordinates, but here, your two series overlap.
Your raw data is :
[
[0,0,0.2, 0.4],
[0,1,0.1, 0.5],
[0,2,0.4, 0.9],
[0,3,0.7, 0.1],
[0,4,0.3, 0.6]
]
From there, you're mapping two series: 2018, and 2019 via the seriesMapping: [{x: 0, y: 1, value: 2}, {x: 0, y: 1, value: 3}] option.
You thus end up with the following two series:
2018 2019 2019 should be
[ [ [
[0, 0, 0.2], [0, 0, 0.4], [1, 0, 0.4],
[0, 1, 0.1], [0, 1, 0.5], [1, 1, 0.5],
[0, 2, 0.4], [0, 2, 0.9], [1, 2, 0.9],
[0, 3, 0.7], [0, 3, 0.1], [1, 3, 0.1],
[0, 4, 0.3] [0, 4, 0.6] [1, 4, 0.6]
] ] ]
Notice that in both cases, the coordinates are the same, but for 2019, the x value should be 1. Since you have 0 as x coordinate for both series, they overlap.
To fix you issue, you need to change your data (or pre-process it, whatever is easier). For example:
var data = '[[0,0,0.2, 0.4],[0,1,0.1, 0.5],[0,2,0.4, 0.9],[0,3,0.7, 0.1],[0,4,0.3, 0.6]]';
var rows = JSON.parse(data);
rows = $.map(rows, function(arr){
return [[
arr[0], arr[1], arr[2], // 2018
arr[0] + 1, arr[1], arr[3], // 2019
]];
});
// and the seriesMapping changes to
data: {
rows: rows,
firstRowAsNames: false,
seriesMapping: [{x: 0, y: 1, value: 2}, {x: 3, y: 4, value: 5}]
},
You can see it in action here: http://jsfiddle.net/Metoule/qgd2ca6p/6/

Lines, circles and other shapes?

Is it possible to draw lines, circles and other shapes with pdfmake? If yes, is there a documentation or are there any samples? I would like to replace jsPDF with pdfmake.
Yes, its possible. pdfmake calls them vectors. See
pdfMake Vector examples for more possibilities.
An example for drawing a line followed by a polygon:
{
canvas:
[
{
type: 'line',
x1: 40, y1: 60,
x2: 260, y2: 60,
lineWidth: 3
},
{
type: 'polyline',
lineWidth: 3,
closePath: true,
points: [{ x: 10, y: 10}, { x: 35, y: 40 }, { x: 100, y: 40 }, { x: 125, y:10 }]
}
]
}
I answered an issue in pdfMake at GitHub like this:
Create a table with layout headeronly you must define an empty body, otherwise the line would not appear:
var dd = {
content: [
{
table : {
headerRows : 1,
widths: [200],
body : [
[''],
['']
]
},
layout : 'headerLineOnly'
}
] }
The width defines the length of the line
canvas: [
{
type: 'rect',
x: 198,
y: -186,
w: 198,
h: 188,
r: 8,
lineWidth: 4,
lineColor: '#276fb8',
},
]
As far as I know, pdfmake does not provide an drawing API. Despite this, pdfmake provides you some methods to insert images at your pdf.
Best regards!

Highcharts: Stacked bar chart display categories data, not series

I have two JS associative arrays that look like this:
var distroDates = [
{
name: 'exDate',
data: [
'06/25/2013',
'12/17/2012',
'06/20/2012',
'12/19/2011',
'06/21/2011',
'12/20/2010',
'06/21/2010',
'12/21/2009',
'06/22/2009',
'12/22/2008',
'06/23/2008',
'12/24/2007',
'12/21/2006',
'12/23/2005',
'12/23/2004',
'12/22/2003',
'12/23/2002'
]
},
{
name: 'Record Date',
data: [
'06/27/2013',
'12/19/2012',
'06/22/2012',
'12/21/2011',
'06/23/2011',
'12/22/2010',
'06/23/2010',
'12/23/2009',
'06/24/2009',
'12/24/2008',
'06/25/2008',
'12/27/2007',
'12/26/2006',
'12/28/2005',
'12/28/2004',
'12/24/2003',
'12/26/2002'
]
},
{
name: 'Payable Date',
data: [
'07/02/2013',
'12/24/2012',
'06/27/2012',
'12/29/2011',
'06/27/2011',
'12/30/2010',
'06/25/2010',
'12/31/2009',
'06/26/2009',
'12/31/2008',
'06/27/2008',
'01/04/2008',
'12/28/2006',
'12/30/2005',
'12/30/2004',
'01/02/2004',
'01/02/2003'
]
}
]
var distroData = [
{
name: 'Income',
data: [
0.3908,
0.4948,
0.2311,
0.3342,
0.245,
0.2213,
0.19,
0.1404,
0.2014,
0.2266,
0.2131,
0.2328,
0.1288,
0.0044,
0.6248,
0,
0
]
},
{
name: 'S.T. Capital Gains',
data: [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
name: 'L.T. Capital Gains',
data: [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
name: 'Return on Capital',
data: [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0.0202,
0,
0,
0
]
}
]
The distroData array is used for the series. The distroDates is used for the categories. I want the categories to display in the tooltip (rather than the usual series data) and I want the tooltip to update with the category values as I hover over each bar along the x-axis.
The following code accomplished this using Highcharts v2.1.4/Highstock v1.2.5 (both are loaded):
tooltip: {
formatter: function() {
var s = '<table class="tooltip distro"><tbody>';
var chart = this.points[0].series.chart; //get the chart object
var categories = chart.xAxis[0].categories; //get the categories array
var index = 0;
var distroDataPoint;
while(this.x !== categories[index]){index++;} //compute the index of corr y value in each data arrays
$.each(distroDates, function(i, categories) { //loop through categories array
s += '<tr><td>'+ categories.name +':</td><td>' + categories.data[index] + '</td></tr>'; //use index to peg categories and distro data to series
distroDataPoint = '<tr><td>Distribution:</td><td>$' + distroData[0].data[index] + '</td></tr>';
});
s += distroDataPoint + '</tbody></table>'
return s;
},
shared: true
},
However, when I upgraded to Highstock 1.3.4 (which includes Highcharts) the while statement locked my browser (FF and Chrome). The browser gave the familiar "Script is non-responsive" alert I assume because it was caught in an expensive loop.
Any ideas how I can rewrite or substitute the while so that performance is acceptable?
Why don't you just use shared tooltip? Just set tooltip.shared = true and everything will work fine. See example how to format shared tooltip here.

Resources