Related
I'm migrating from ChartJS 2.9.3 to 4.2.1 (current). By following the 3.x and 4.x migration guides, I've sorted most things out, but I've come across a problem that I don't see a solution for.
One of my charts is a stacked bar chart. There are two datasets for it:
let chartData = {
// other stuff...
datasets: [
{ label: "Has thing", data: [200, 250, etc] },
{ label: "Does not has thing", data: [10, 4, etc] },
]
}
In my tooltips, I was accessing both datasets to create a custom tooltip with the percent representation of each part of each stack. For instance, the tooltips for the first stack might say: "Has thing: 200 (95.2%)" and "Does not has thing: 10 (4.8%)". My callback function looked like this:
// other stuff
callbacks: {
label: function(tooltipItem, data) {
let dataset = data.datasets[tooltipItem.datasetIndex];
let count_with = data.datasets[0].data[tooltipItem.index]
let count_without = data.datasets[1].data[tooltipItem.index]
let total = count_with + count_without
let this_dataset_count = dataset.data[tooltipItem.index]
let this_dataset_perc = (this_dataset_count / total * 100).toFixed(1)
let label = dataset.label + ": "
label += this_dataset_count + " (" + this_dataset_perc + "%)"
return label;
}
}
Looking at the 3.x migration guide, it appears they removed the data parameter from the tooltip callback, opting instead to add the item's dataset directly to the tooltipItem. Unfortunately, they don't seem to specify how I can access other datasets.
Has this functionality simply been removed completely, or is there a different way to access it?
As #kikon pointed out, data is accessible via context.chart.data. For some reason, that doesn't show up for me when I console.dir() the context object so I was just completely overlooking it.
Anyway, for anyone this might help in the future, here's the working version:
callbacks: {
label: function(context) {
const datasets = context.chart.data.datasets
const countWith = datasets[0].data[context.dataIndex]
const countWithout = datasets[1].data[context.dataIndex]
const perc = (context.raw / (countWith + countWithout) * 100).toFixed(1)
return `${context.dataset.label}: ${context.formattedValue} (${perc}%)`
}
}
I collect data from my arduino sensors into a mysql table then use google graphs to draw line charts with the data. My data is temperature, humidity and a calculated humidex. Part of my project was to draw a guage with the current values which i was able to do. So I select the data into a data table, then filter them using a data view. Then draw three separate guages for each of the filtered values. The problem is temperatures usually go from -40 to 40 let's say and humidity from 0 to 100%, so drawing them in the same graph makes the graph looks flat as far as the temperatures are concerned.
My goal is to draw two graphs without having to query the database twice (if possible of course)
Below is the relevant code
echo "[new Date(" . $row["Year"] .", " .$row["Month"].", " .$row["Day"].", " .$row["Hour"].", " .$row["Min"]."), ". $temperature . ", " . $humidex . ", " . $humidity . "]";
var temp_view = new google.visualization.DataView(data);
temp_view.setRows([0,1,2]);
var humid_view = new google.visualization.DataView(data);
humid_view.setRows([0,3]);
var options = {'title':'Temperature', 'width':650, 'height':500};
var chart1 = new google.visualization.LineChart(document.getElementById('temp_chart'));
chart1.draw(temp_view, options);
var chart2 = new google.visualization.LineChart(document.getElementById('humid_chart'));
chart2.draw(humid_view, options);
if the data results in an array similar to the following...
[new Date(2018, 5, 29, 7, 26), 70, 75, 0.98]
then you only have one row with 4 columns
which means you should be using setColumns, instead of setRows...
try...
var temp_view = new google.visualization.DataView(data);
temp_view.setColumns([0,1,2]);
var humid_view = new google.visualization.DataView(data);
humid_view.setColumns([0,3]);
var options = {'title':'Temperature', 'width':650, 'height':500};
var chart1 = new google.visualization.LineChart(document.getElementById('temp_chart'));
chart1.draw(temp_view, options);
var chart2 = new google.visualization.LineChart(document.getElementById('humid_chart'));
chart2.draw(humid_view, options);
Take a look at this google-chart:
<google-chart id='c1' type='line' options='{"title": "Example"}'></google-chart>
To fill it with data I can do:
document.getElementById("c1").data = [["Date", "Value"], ["01.01.2016", 100]];
However, I am not able to append data, this does not work:
document.getElementById("c1").data.push(["02.01.2016", 200]);
How can I push/splice data to/from it?
I want to push a value by WebSocket regularly and remove the oldest one in the same time.
Update 1
I have tried replacing the data with rows and cols.
rows is a plain array, you can just push data to it, which works, partly.
In fact, before the chart is drawn, you can use document.getElementById("xy").rows.push(); and it works (That is, once the chart is drawn, it includes the pushed rows.
But, after the chart has been drawn, push does not work anymore. The update to rows is silently swallowed and results in no update of the chart.
Note that calling document.getElementById("c1").drawChart(); also does not update the chart's view.
Update 2
As suggested by #Ümit I have tried the following two, unfortunately both without success:
var chart = document.getElementById("c1");
chart.data = [["Year", "Things", "Stuff"], ["2004", 1000, 400], ["2005", 1170, 460], ["2006", 660, 1120], ["2007", 1030, 540]];
window.setTimeout(function() {
chart.push('data',["2008", 200, 999]);
chart.drawChart();
console.log("Pushed");
}, 1000);
var chart = document.getElementById("c1");
chart.cols = [{label: "Category", type: "string"}, {label: "Value", type: "number"}];
chart.rows = [["Category 1", Math.random() * 2], ["Category 2", Math.random() * 2]];
window.setTimeout(function() {
chart.push('rows', ["Category 3", Math.random() * 2]);
chart.drawChart();
console.log("Pushed");
}, 1000);
Update 3
Third try, replacing the complete rows, without success:
var chart = document.getElementById("c1");
chart.cols = [{label: "Category", type: "string"}, {label: "Value", type: "number"}];
chart.rows = [["Category 1", Math.random() * 2], ["Category 2", Math.random() * 2]];
window.setTimeout(function() {
var temp = chart.rows;
//console.log(temp);
temp.push(["Category 3", Math.random() * 2]);
//console.log(temp);
chart.push('rows', temp); // does not work
chart.rows = temp; // does also not work
chart.drawChart();
console.log("Pushed");
}, 1000);
Because you are dealing with Arrays and Objects respectively you need to use the array/object mutation functions of Polymer to ensure that the observer in google-chart is called and the chart gets updated.
So in your code you should do this:
document.getElementById("c1").push('data',["02.01.2016", 200]);
or
document.getElementById("xy").push('row',["02.01.2016", 200]);
Update:
The problem is that google-chart relies on normal property observers for data, rows and cols which will only work if you replace the entire array/object (see this SO answer).
So either you can create an issue in the google-chart repo or re-create data/rows
This is the working solution:
var chart = document.getElementById("c1");
chart.data = [["Year", "Things", "Stuff"], ["2004", 1000, 400], ["2005", 1170, 460], ["2006", 660, 1120], ["2007", 1030, 540]];
window.setTimeout(function() {
var temp = [["Year", "Things", "Stuff"], ["2004", 1000, 400], ["2005", 1170, 460], ["2006", 660, 1120], ["2007", 1030, 540], ["2008", 200, 999]];
//chart.push("data", temp); // does not work
chart.data = temp; // works
//chart.drawChart(); // is not necessary
console.log("Pushed");
}, 1000);
Have an error as follows:
Terminating app due to uncaught exception 'NameError', reason: 'weather_controller.rb:3:in `viewDidLoad': uninitialized constant WeatherController::Name (NameError)
AppDelegate:
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
puts "Hello! You just launched: #{App.name}, \n location: (#{App.documents_path})"
#window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
#window.backgroundColor = UIColor.lightGrayColor
#window.rootViewController = MyController.alloc.init
#window.makeKeyAndVisible
true
end
end
my_controller.rb:
class MyController < UIViewController
def viewDidLoad
#name_label = setup_label [[10, 10], [300, 50]], UIColor.orangeColor, Name
#place_label = setup_label [[10, 80], [300, 50]], UIColor.yellowColor, Place
#temp_label = setup_label [[10, 150], [300, 50]], UIColor.greenColor, Temperature
end
def setup_label frame, bgcolor, text
label = UILabel.alloc.initWithFrame(frame)
label.textColor = UIColor.darkGrayColor
label.backgroundColor = bgcolor
label.textAlignment = UITextAlignmentCenter
label.text = text.to_s
view.addSubview label
label
end
end
Any ideas? Thanks in advance
In your setup_label method, you're accepting the following arguments, frame, bgcolor and text where your text argument suppose to be a String object.
Therefore, your viewDidLoad method should be the following
def viewDidLoad
#name_label = setup_label [[10, 10], [300, 50]], UIColor.orangeColor, "Name"
#place_label = setup_label [[10, 80], [300, 50]], UIColor.yellowColor, "Place"
#temp_label = setup_label [[10, 150], [300, 50]], UIColor.greenColor, "Temperature"
end
I was wondering how I can make a simple bar chart that perhaps has day as the x-axis, with values 'today' and 'yesterday', and the y-axis as perhaps 'time' with corresponding values '1' and '2'. I guess I'm confused as to how to set text as the values for the x-axis, how to show the y axis, and what exactly r.g.axis does...
(I found an example using axis = r.g.axis(0,300,400,0,500,8,2) and I only know it's the xpos, ypos,width, ??, ?? num ticks, ??). Any insight would be great! Or a page with more fully featured bar chart examples (labels, etc). Thanks.
For the sake of all those googling this:
r.g.axis(x_start, y_start, x_width, from, to, steps, orientation, labels, type, dashsize)
x_start and y_start: distance of the axis text from the bottom left corner
x_width: position of the end of the text along the x axis
from and to: used to specify and range to use instead of using the labels argument
steps: is the number of ticks - 1
orientation: seems to specify x-axis vs. y-axis
type: is the type of tick mark used.
This was all deduced from the source code. I think I'll be switching to a charting library with documentation now...
The current code (Raphaeljs 2.0) has changed and has to be slightly adapted to use Raphael.g.axis instead of r.g.axis:
Raphael.g.axis(85,230,310,null,null,4,2,["Today", "Yesterday",
"Tomorrow", "Future"], "|", 0, r)
You're on the right track. You use g.axis and the positional arguments for setting the text is found in the 'text' arg (positional) and for toggling the y using the 'orientation' args. I added an example here,
Barchart with text x-axis
Reading this Q&A and a dozen like it, I still could not get gRaphaël to show proper labels for a bar chart. The recipes all seemed to refer to older versions of the library, or to github pages that are no longer there. gRaphaël produces some great looking output--but its docs leave much to be desired.
I was, however, able to use a combination of Firebug and Inspect This Element to follow the code and see what it produced. Diving into the barchart object, the required geometry is right there. To save others the frustration, here's how I solved the problem:
<script>
function labelBarChart(r, bc, labels, attrs) {
// Label a bar chart bc that is part of a Raphael object r
// Labels is an array of strings. Attrs is a dictionary
// that provides attributes such as fill (text color)
// and font (text font, font-size, font-weight, etc) for the
// label text.
for (var i = 0; i<bc.bars.length; i++) {
var bar = bc.bars[i];
var gutter_y = bar.w * 0.4;
var label_x = bar.x
var label_y = bar.y + bar.h + gutter_y;
var label_text = labels[i];
var label_attr = { fill: "#2f69bf", font: "16px sans-serif" };
r.text(label_x, label_y, label_text).attr(label_attr);
}
}
// what follows is just setting up a bar chart and calling for labels
// to be applied
window.onload = function () {
var r = Raphael("holder"),
data3 = [25, 20, 13, 32, 15, 5, 6, 10],
txtattr = { font: "24px 'Allerta Stencil', sans-serif", fill: "rgb(105, 136, 39)"};
r.text(250, 10, "A Gratuitous Chart").attr(txtattr);
var bc = r.barchart(10, 10, 500, 400, data3, {
stacked: false,
type: "soft"});
bc.attr({fill: "#2f69bf"});
var x = 1;
labelBarChart(r, bc,
['abc','b','card','d','elph','fun','gurr','ha'],
{ fill: "#2f69bf", font: "16px sans-serif" }
);
};
</script>
<div id="holder"></div>
There are a bunch of little cleanups you could do to labelBarChart(), but this basically gets the job done.
Here's a function I wrote for adding the labels. It's not particularly elegant but it will add the labels:
Raphael.fn.labelBarChart = function(x_start, y_start, width, labels, textAttr) {
var paper = this;
// offset width and x_start for bar chart gutters
x_start += 10;
width -= 20;
var labelWidth = width / labels.length;
// offset x_start to center under each column
x_start += labelWidth / 2;
for ( var i = 0, len = labels.length; i < len; i++ ) {
paper.text( x_start + ( i * labelWidth ), y_start, labels[i] ).attr( textAttr );
}
};
Usage is as follows:
var paper = Raphael(0, 0, 600, 400);
var chart = paper.barchart(0, 0, 600, 380, [[63, 86, 26, 15, 36, 62, 18, 78]]);
var labels = ['Col 1', 'Col 2', 'Col 3', 'Col 4', 'Col 5', 'Col 6', 'Col 7', 'Col 8'];
paper.labelBarChart(0, 390, 600, labels, {'font-size': 14});
I would like to propose a solution of an issue of the labelBarChart function proposed by Jonathan Eunice.
considering stacked bar-graphes (or other bar-graphes with more than one array of values), I added a test on bc.bars[0] in case the bc.bars.length means the number of arrays of values stacked.
This lead to the code :
<script>
function labelBarChart(r, bc, labels, attrs) {
// Label a bar chart bc that is part of a Raphael object r
// Labels is an array of strings. Attrs is a dictionary
// that provides attributes such as fill (text color)
// and font (text font, font-size, font-weight, etc) for the
// label text.
//Added test : replace bc.bars by generic variable barsRef
var barsRef = (typeof bc.bars[0].length === 'undefined') ? bc.bars : bc.bars[0];
var bar, gutter_y, label_x, label_y, label_text;
//Added consideration of set attrs (if set)
var label_attr = (typeof attrs === 'undefined') ? {} : attrs;
label_attr['fill'] = (typeof label_attr['fill'] === 'undefined') ? "#2f69bf" : label_attr['fill'];
label_attr['font'] = (typeof label_attr['font'] === 'undefined') ? "16px sans-serif" : label_attr['font'];
for (var i = 0; i<barsRef.length; i++) {
bar = barsRef[i];
gutter_y = bar.w * 0.4;
label_x = bar.x
label_y = bar.y + bar.h + gutter_y;
label_text = labels[i];
r.text(label_x, label_y, label_text).attr(label_attr);
}
}
// what follows is just setting up a bar chart and calling for labels
// to be applied
// I added an array of data to illustrate : data4
window.onload = function () {
var r = Raphael("holder"),
data3 = [25, 20, 13, 32, 15, 5, 6, 10],
data4 = [0, 2, 1, 40, 1, 65, 46, 11],
txtattr = { font: "24px 'Allerta Stencil', sans-serif", fill: "rgb(105, 136, 39)"};
r.text(250, 10, "A Gratuitous Chart").attr(txtattr);
var bc = r.barchart(10, 10, 500, 400, [data3, data4] {
stacked: true,
type: "soft"});
bc.attr({fill: "#2f69bf"});
labelBarChart(r, bc,
['abc','b','card','d','elph','fun','gurr','ha'],
{ fill: "#2f69bf", font: "16px sans-serif" }
);
};
</script>
<div id="holder"></div>
I just tested it with 2 arrays of values stacked.