I have a Web App which uses Google Charts.
There are more than one chart on a page.
I successfully create and render the charts.
Depending on the user's filters, I receive new chart data via Ajax.
How can I reacquire a chart object and update it, if I don't keep the returned object that far in the code?
I wonna do something similar to the following:
function DrawChart()
{
// Code code code ... more code
// Initialize
var chart = new google.visualization.ScatterChart(document.getElementById("my-chart-div"));
// Draw
chart.draw(data, options);
}
And later on:
function UserDidSomething()
{
var newData = MyAjaxCall(...);
var options = ...;
var chart = ...; // What goes here??
chart.draw(newData, options);
}
Thanks in advance,
Shy.
I created a dynamic charts object that keeps the created charts:
/// <summary>
/// This object holds created charts in order to edit them.
/// The key for the chart is the div id (e.g. charts["chart-my-chartname"]).
/// </summary>
var charts = {};
function ChartCreated(divId)
{
return charts[divId] !== undefined && charts[divId] != null;
}
function GetChart(divId)
{
return charts[divId];
}
function AddChart(divId, chart)
{
charts[divId] = chart;
}
function RemoveChart(divId)
{
charts[divId] = null;
}
function CreateOrUpdateChart(divId, chartType, data, options)
{
var chart;
// If the chart was previously created, use its object
if (ChartCreated(divId))
{
chart = GetChart(divId);
}
else // If there was no chart, create and keep it
{
chart = InitializeNewChart(chartType, divId);
AddChart(divId, chart);
}
// Create a new DataTable object using the JavaScript Literal Initializer, and the received JSON data object
data = new google.visualization.DataTable(data);
// Render chart
chart.draw(data, options);
}
function InitializeNewChart(type, divId)
{
var container = document.getElementById(divId);
switch (type)
{
case "Scatter": return new google.visualization.ScatterChart(container);
case "Column": return new google.visualization.ColumnChart(container);
case "Line": return new google.visualization.LineChart(container);
default: return null;
}
}
Related
I am using chart.js v2.5.0.
I want to create charts dynamically without using a global variable.
For example, I want to use code similar to this:
https://jsfiddle.net/DUKEiLL/sf57xw6b/
function UpdateChart(ctrl) {
var config = $("#" + ctrl).data("ChartJs");
config.data.datasets.forEach(function (dataset) {
dataset.data = dataset.data.map(function () {
return randomScalingFactor();
});
});
var ctx = document.getElementById(ctrl).getContext("2d");
var TempMyDoughnut = new Chart(ctx, config);
TempMyDoughnut.update();
}
But it doesn't work properly: when the user presses "update" button and hovers over the chart, previous instance are suddenly displayed.
Since you are creating a new chart on each execution of UpdateChart function, hence you would have to destroy any previous instance of chart to prevent the hover issue.
To accomplish so, you could simply replace your UpdateChart function with the following ...
function UpdateChart(ctrl) {
var config = $("#" + ctrl).data("ChartJs");
config.data.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
// destroy previous instance of chart
var meta = config.data.datasets[0]._meta;
for (let i in meta) {
if (meta[i].controller) meta[i].controller.chart.destroy();
}
var ctx = document.getElementById(ctrl).getContext("2d");
var TempMyDoughnut = new Chart(ctx, config);
}
Here is the working example on jsFiddle
I'm trying to get either options or, ideally, dynamicTable passed from initializeTable to the applyTableFilters function and I'm having problems getting the expected values. I'm using List.js to make a table dynamic and I need to pass or recreate the dynamicTable object so I can go ahead and use it to filter the table.
Here is the function that creates the List.js object from the HTML table:
function initializeTable(options) { // initializes table to be dynamic using List.js functions
var dynamicTable = new List("table-content", options);
dynamicTable.on("updated", function (list) { // writes a message to the user if no results are found
if (list.matchingItems.length == 0) {
document.getElementById("no-results").style.display = "block";
}
else {
document.getElementById("no-results").style.display = "none";
}
});
console.log(dynamicTable);
console.log(options);
console.log(arguments.length);
applyTableFilters.bind();
}
I've tried different methods to pass the variables to the function below. I tried .call, applyTableFilters(args), and .apply, but the problem is that I do not want the function to execute from inside here, only when the click event from the button goes off (not shown in these functions).
This is the function I want to pass the object to and proceed to make the filter functions using it:
function applyTableFilters(dynamicTable) {
var form = document.getElementById("filter-form");
//console.log(options);
//var dynamicTable = new List("table-content", options);
console.log(dynamicTable);
var filters = form.querySelectorAll('input[type="checkbox"]:checked');
dynamicTable.filter(function (item) {
console.log(item);
console.log(item._values);
if (item.values().id == 2) {
return true;
}
else {
return false;
}
//var filterStrings = [];
//console.log(filters);
//for (var i = 0; i < filters.length; i++) {
// var filterVal = filters[i].value;
// var filterString = "(" + item.values().column == filterVal + ")"; // filterVal.contains(item.values().column) ||
// filterStrings.push(filterString);
// console.log(filterVal);
// console.log(filterString);
//}
//console.log(filterStrings);
//var filterString = filterStrings.join(" && ");
//console.log(filterString);
//return filterString;
});
}
I've used:
applyTableFilters.bind(this, dynamicTable/options);
applyTableFilters.bind(null, dynamicTable/options);
applyTableFilters.bind(dynamicTable/options);
Switching between the two since I don't need both passed if one ends up working, etc. I always get a mouse event passed in and that's not even the right type of object I'm looking for. How can I get the right object passed? Also all the values in the first function are not empty and are populated as expected so it's not the original variables being undefined or null. Thanks in advance.
From your initializeTable function return a function that wraps the applyTableFilters function with the arguments you want.
Then assign the returned function to a var to be executed later.
function initializeTable(options) {
var dynamicTable = new List("table-content", options);
// other stuff
return function () {
applyTableFilters(dynamicTable)
}
}
// other stuff
var applyTableFiltersPrep = initializeTable(options)
// later, when you want to execute...
applyTableFiltersPrep()
JSFiddle example
I want to get the data points which cannot be plotted on the underlying map (i.e. joinBy fails to map the data to the geojson). Is there any way to get the unplotted data?
You can check all points and find which are not plotted, the condition is that point has a value but doesn't have graphic:
chart: {
events: {
load: function () {
var chart = this,
unplottedPoints = [];
$.each(chart.series[0].data, function (i, point) {
if (point.value && !point.graphic) {
unplottedPoints.push(point);
}
});
console.log(unplottedPoints);
}
}
},
In array unplottedPoints you have list of all not rendered points.
Demo: http://jsfiddle.net/spmx9xu3/1/
I just wrote a very simple snippet to understand how jQuery data() functions and the code is as follows:
$(function () {
carousel = function() {
this.prop1 = 1;
this.prop2 = 'two';
this.prop3 = 3;
}
var _str = $('#test'),
$str_data = _str.data();
console.log($str_data);
data = _str.data('carousel');
if (!data) _str.data('carousel' , new carousel());
console.log(data);
if (!data) {
console.log('no data');
}
});
Now, the objective of this code was to add data() to the div element using the new operator and then checking if that piece of data was added, however in my snippet of code in-spite of me adding data to the div element using the below line of code:
if (!data) _str.data('carousel' , new carousel());
When I checked again to see on the next line if data is actually added:
if (!data) {
console.log('no data');
}
The test passes, which means no data was added. So what am I missing?
If there is no data, you are updating the data associated with the element but the value referred by the data variable is not updated that is why it is still giving undefined as its value.
$(function () {
var carousel = function () {
this.prop1 = 1;
this.prop2 = 'two';
this.prop3 = 3;
}
var _str = $('#test'),
$str_data = _str.data();
console.log($str_data);
var data = _str.data('carousel');
if (!data) {
//create a new carousel and assign it to data so that it gets a new value
data = new carousel();
//store the new carousel value
_str.data('carousel', data);
}
console.log(data);
if (!data) {
console.log('no data');
}
});
Demo: Fiddle
The problem is that you are not updating the data variable's value. You need either to set again the data value after setting the carousel or to call directly the jquery function .data() as the example bellow:
data = _str.data('carousel');
// this condition is not updating the variable defined above
if (!data) {
_str.data('carousel' , new carousel());
}
console.log(data);
// you have to update the variable value or to call as
if (!_str.data('carousel')) {
console.log('no data');
}
I understand that in D3, dispatch can be used to fire events to multiple visualisations according to this example.
I also understand that if I want to call a dispatch from an object and pass in the context, I can use apply as shown here.
However, I'm having a hard time combining the arguments from a D3 dispatch and the context that I want.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse);
// this next line isn't working, since the
// console.log in probeLoad still returns undefined
probeDispatch.probeLoad.apply(this);
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
new genChart("pants");
new genChart("shirt");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
It does set the context properly when you see "pants" in the console.
But then there are 3 undefined's logged, because you also call
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
without supplying context.
You need
probeDispatch.probeLoad.apply(instanceOfGenChart, [lines[line_count][0], lines[line_count][1]]);
But enabling that also requires moveing parse_dispatch down the page.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse);
// this next line isn't working, but I don't know what to do
probeDispatch.probeLoad.apply(this);
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
var instanceOfGenChart = new genChart("pants");
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad.apply(instanceOfGenChart, [lines[line_count][0], lines[line_count][1]]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
So it turns out to bring the context into the function, I have to bind() it for reasons I'm not too clear on.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse.bind(this));
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
new genChart("pants");
new genChart("shirt");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Added by meetamit
Bind is the solution here, because it locks a scope to an "instance" of genChart.prototype. probeParse. This way parse_dispatch (the invoker) doesn't need to know anything about scope. It's equivalent to this:
// my chart object
var genChart = function(label){
this.label = label;
var self = this;
var probeParseBound = function() { self.probeParse(); };
probeDispatch.on(("probeLoad."+this.label), probeParseBound);
};