I'm building a web application using Vite.js + HTML/CSS/JS and jQuery. I want to embed tweets on a page using lists of twitter post id's returned from an API. I am using Twitter's Javascript Embed Tweets factory function to achieve this and encountering a strange problem. Everything works when the HTML divs are in a certain order, but if I try to move things around, one of the list of tweets doesn't display properly.
To explain further: I have two HTML divs that I'm using as containers for the tweets -- the first container displays tweets from a list of "positive" posts, and the second container displays tweets from a list of "negative" posts (the API returns these lists). When the page loads, the tweets are populated, but remain hidden. The user must click a button "display negative tweets" or "display positive tweets" in order toggle them to hidden/visible.
Now, if I arrange the HTML divs as follows, everything works properly:
Negative Tweets Container
Positive Tweets Container
However, if I reverse the order of the containers, the negative tweets stop displaying on button click:
Positive Tweets Container
Negative Tweets Container
It's quite strange, you'd think that whichever div was placed on the bottom would have the same issue, but it's only the negative tweets container that stops working properly when placed on the bottom. The code I'm using to populate and display the tweets for each section is identical except for the html id tags and variable names, so it doesn't make sense to me that the order of HTML divs would cause an issue. I've logged everything in the console, there are no errors and no indication of what is causing the problem.
I recreated the issue in two codepens for convenience:
Negative Div on Top (everything works)
Negative Div on Bottom (negative tweets won't show)
Please note, the two codepen projects are identical except for the order of HTML divs, so you can just look at one if you want, all you have to do is try putting the negative tweets container below the positive tweets container, or vice versa, and you will see the issue.
Here are code snippets as well. This is the working version, if you switch the order of tweet containers, the negative tweets will stop displaying properly:
//////////////////////////////////////////
// On page load, populate tweets, but they will remain
// hidden until user clicks "display" button
//////////////////////////////////////////
$(function() {
populatePositiveTweets();
populateNegativeTweets();
});
//////////////////////////////////////////
// test data -- positive
//////////////////////////////////////////
const positiveData = {
brand: "serena",
logo: "nike",
match_count: 5,
post_id_list: [{
post_id: "1400014236013764615"
},
{
post_id: "1399610411557683201"
},
{
post_id: "1399594614093271041"
},
{
post_id: "1399566283469819907"
},
{
post_id: "1399538218060500997"
}
],
return_error: null
};
//////////////////////////////////////////
// test data -- negative
//////////////////////////////////////////
const negativeData = {
brand: "serena",
logo: "nike",
match_count: 5,
post_id_list: [{
post_id: "1400014236013764615"
},
{
post_id: "1399610411557683201"
},
{
post_id: "1399594614093271041"
},
{
post_id: "1399566283469819907"
},
{
post_id: "1399538218060500997"
}
],
return_error: null
};
//////////////////////////////////////////
// function to populate positive tweets
//////////////////////////////////////////
const populatePositiveTweets = () => {
// Store the list of post id's in variable
const positiveTweetsList = positiveData.post_id_list;
console.log(positiveTweetsList);
// set up the variables
var subs;
var tempString;
var col1 = "",
col2 = "",
col3 = "";
var column_index = 1;
// loop through the results to build the three column strings
for (subs = 0; subs < positiveTweetsList.length; subs++) {
if (column_index == 1) {
col1 += "<div class='embed-responsive' id='tweetArea" + subs + "'></div>";
} else if (column_index == 2) {
col2 += "<div class='embed-responsive' id='tweetArea" + subs + "'></div>";
} else if (column_index == 3) {
col3 += "<div class='embed-responsive' id='tweetArea" + subs + "'></div>";
}
column_index += 1;
if (column_index == 4) {
column_index = 1;
}
}
// write out the column html code
document.getElementById("tweetCol1__positive").innerHTML = col1;
document.getElementById("tweetCol2__positive").innerHTML = col2;
document.getElementById("tweetCol3__positive").innerHTML = col3;
// loop through the results to display the tweets
for (subs = 0; subs < positiveTweetsList.length; subs++) {
// get the post id
post_id = positiveTweetsList[subs];
// build the twitter area name
twitter_area = "tweetArea" + subs;
// display the tweets
twttr.widgets
.createTweet(
positiveTweetsList[subs].post_id,
document.getElementById(twitter_area), {}
)
.then(function(e1) {
window.setTimeout(hideOverlay, 5000);
})
.catch(function(e2) {});
}
};
//////////////////////////////////////////
// function to populate negative tweets
///////////////////////////////////////////
const populateNegativeTweets = () => {
// Store the list of post id's in variable
const negativeTweetsList = negativeData.post_id_list;
// set up the variables
var subs;
var tempString;
var col1 = "",
col2 = "",
col3 = "";
var column_index = 1;
// loop through the results to build the three column strings
for (subs = 0; subs < negativeTweetsList.length; subs++) {
if (column_index == 1) {
col1 += "<div class='embed-responsive' id='tweetArea" + subs + "'></div>";
} else if (column_index == 2) {
col2 += "<div class='embed-responsive' id='tweetArea" + subs + "'></div>";
} else if (column_index == 3) {
col3 += "<div class='embed-responsive' id='tweetArea" + subs + "'></div>";
}
column_index += 1;
if (column_index == 4) {
column_index = 1;
}
}
// write out the column html code
document.getElementById("tweetCol1__negative").innerHTML = col1;
document.getElementById("tweetCol2__negative").innerHTML = col2;
document.getElementById("tweetCol3__negative").innerHTML = col3;
// loop through the results to display the tweets
for (subs = 0; subs < negativeTweetsList.length; subs++) {
// get the post id
post_id = negativeTweetsList[subs];
// build the twitter area name
twitter_area = "tweetArea" + subs;
// display the tweets
twttr.widgets
.createTweet(
negativeTweetsList[subs].post_id,
document.getElementById(twitter_area), {}
)
.then(function(e1) {
window.setTimeout(hideOverlay, 5000);
})
.catch(function(e2) {});
}
};
//////////////////////////////////////////
// Function to toggle tweets to show/hide
//////////////////////////////////////////
const toggleTweets = (type) => {
let tweetsContainer = $(`#tweets-container__${type}`);
if ($(tweetsContainer).css("display") === "none") {
$(tweetsContainer).show();
console.log("tweets container", $(tweetsContainer).css("display"));
} else {
$(tweetsContainer).hide();
console.log("tweets container", $(tweetsContainer).css("display"));
}
};
//////////////////////////////////////////
// Attach toggle function to buttons as click listeners
//////////////////////////////////////////
$("#tweets-btn__positive").on("click", () => {
console.log("toggle positive tweets");
toggleTweets("positive");
});
$("#tweets-btn__negative").on("click", () => {
console.log("toggle negative tweets")
toggleTweets("negative");
});
#tweets-container__negative,
#tweets-container__positive {
display: none;
width: auto;
}
#no-data__negative-posts,
#no-data__positive-posts {
display: none;
}
#parent-container {
display: block;
}
<!-- CSS Styles -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous">
<!-- jQuery Scripts -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- Bootstrap JavaScript Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<!-- JS Add-on Scripts -->
<script src="https://platform.twitter.com/widgets.js"></script>
<div id="parent-container">
<!-- Negative Button -->
<button id="tweets-btn__negative">Toggle Negative Posts</button>
<!-- Negative Tweets Container-->
<div id="tweets-container__negative">
<div class="container-fluid" id="negative-tweets">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-3" id="tweetCol1__negative"></div>
<div class="col-md-1"></div>
<div class="col-md-3" id="tweetCol2__negative"></div>
<div class="col-md-1"></div>
<div class="col-md-3" id="tweetCol3__negative"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Positive Button -->
<button id="tweets-btn__positive">Toggle Positive Posts</button>
<!-- Positive Tweets Container-->
<div id="tweets-container__positive">
<div class="container-fluid" id="positive-tweets">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-3" id="tweetCol1__positive"></div>
<div class="col-md-1"></div>
<div class="col-md-3" id="tweetCol2__positive"></div>
<div class="col-md-1"></div>
<div class="col-md-3" id="tweetCol3__positive"></div>
</div>
</div>
</div>
</div>
</div>
</div>
Lastly, Here's one more link to an example of the Twitter embed code that I used as a reference for my code: https://www.labnol.org/code/19933-embed-tweet-with-javascript
I know this is a complex problem, I did my best to create a minimal reproduction of the issue and provide relevant resources. Thanks in advance for any help.
As #bravo pointed out in the comments, I had duplicated id's in my HTML:
As they said,
"the issue is with id='tweetArea" + subs ... and twitter_area =
"tweetArea" + subs; and document.getElementById(twitter_area) will
fail to get the right element half the time"
I am trying to link my HTML form with my csv file to populate form field automatically. Based on what user selects in first field, second field should be automatically filled with the appropriate value. when the user starts typing in the first field, the input field automatically pulls data from csv file to show available options. Options appear after user completes writing 3 words in the field.
Further, to avoid any CORS issue in code, I have added additional URL in my CSV file URL which makes it accessible by any web application.
I was able to prepare this code with the help of examples available on web. However, my code is not working properly. I tried to solve this problem on my own. But I don't know about coding enough.
Can anyone please help me to solve this problem.
<script>
$(function() { function processData(allText) { var record_num = 2;
// or however many elements there are in each row
var allTextLines = allText.split(/\r\n|\n/); var lines = []; var headings = allTextLines.shift().split(','); while (allTextLines.length > 0) { var tobj = {}, entry; entry = allTextLines.shift().split(','); tobj['label'] = entry[0]; tobj['value'] = entry[1]; lines.push(tobj); } return lines; }
// Storage for lists of CSV Data
var lists = [];
// Get the CSV Content
$.get("https://cors-anywhere.herokuapp.com/www.coasilat.com/wp-content/uploads/2019/06/file.txt ", function(data) { lists = processData(data); }); $("#species").autocomplete({ minLength: 3, source: lists, select: function(event, ui) { $("#species").val(ui.item.label); $("#identifiant").val(ui.item.value); return false; } }); });)
</script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<form>
<div class="ui-widget"> <label for="species">Species: </label> <input id="species"> <label for="identifiant">Identifiant: </label> <input id="identifiant" style="width: 6em;"> </div></form>
Here's the modified answer, working with jquery-ui autocomplete.
The solution: the $.get() is an asynchronous function (the data is not readily available on page load), so jquery-ui autocomplete didn't work with the updated lists[] array, because it (seems so that it) doesn't work with dynamically generated data. So the source of autocomplete had to be refreshed with the newly arrived data in the $.get()'s callback function.
$("#species").autocomplete('option', 'source', lists) - this is the key line, as it updates autocomplete's source with the new data.
// Only needed for working example
var myCSV = "Species,Identifiant\r\n";
myCSV += "Species A,320439\r\n";
myCSV += "Species B,349450\r\n";
myCSV += "Species C,43435904\r\n";
myCSV += "Species D,320440\r\n";
myCSV += "Species E,349451\r\n";
myCSV += "Species F,43435905\r\n";
console.log(myCSV);
// Begin jQuery Code
$(function() {
function processData(allText) {
// var record_num = 2; // or however many elements there are in each row
var allTextLines = allText.split(/\r\n|\n/);
var lines = [];
var headings = allTextLines.shift().split(',');
while (allTextLines.length > 0) {
var tobj = {},
entry;
entry = allTextLines.shift().split(',');
/*
Normally we'd read the headers into the object.
Since we will be using Autocomplete, it's looking for an array of objects with 'label' and 'value' properties.
tobj[headings[0]] = entry[0];
tobj[headings[1]] = entry[1];
*/
if (typeof entry[1] !== 'undefined') {
let prefix = !entry[0].includes('Species') ? 'Species ' : ''
tobj['label'] = prefix + entry[0];
tobj['value'] = entry[1].trim();
lines.push(tobj);
}
}
return lines;
}
let lists = [];
// For working example
// lists = processData(myCSV);
// console.log('lists1', lists)
// In your script you will get this content from the CSV File
// Get the CSV Content
$.get("https://cors-anywhere.herokuapp.com/www.coasilat.com/wp-content/uploads/2019/06/file.txt", function(data) {
lists = processData(data);
$("#species").autocomplete('option', 'source', lists)
console.log('lists2', lists)
});
$("#species").autocomplete({
minLength: 3,
source: lists,
focus: function(event, ui) {
console.log(ui)
$("#species").val(ui.item.label);
return false;
},
select: function(event, ui) {
$("#species").val(ui.item.label);
$("#identifiant").val(ui.item.value);
return false;
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" />
<div class="ui-widget">
<label for="species">Species: </label>
<input id="species">
<label for="identifiant">Identifiant: </label>
<input id="identifiant" style="width: 6em;">
</div>
The processData() function didn't work as expected with the source you provided, so that had to be modified too.
My solution is a kinda' autocomplete - it's called typeahead.
I displayed the filtered list, so you see what's happening, but you can place that anywhere - in a dropdown below the input field, for example.
$(function() {
// processing CSV data
function processData(allText) {
// splitting lines
var allTextLines = allText.split(/\r\n|\n/);
const speciesData = []
// reading data into array, if it's not the first row (CSV header) AND
// if it's not 'Species'
let j = 0; // this will be the item's index
for (let i = 0; i < allTextLines.length - 1; i++) {
if (i !== 0 && allTextLines[i] !== 'Species') {
const record = allTextLines[i].split(',')
speciesData.push({
label: record[0],
value: record[1].trim(), // it has a lot of whitespace
index: j // adding this, so we can keep track of items
})
j++; // incrementing index
}
}
// returning processed data
return speciesData;
}
// Storage for lists of processed CSV Data
let lists = [];
// Get the CSV Content
$.get("https://cors-anywhere.herokuapp.com/www.coasilat.com/wp-content/uploads/2019/06/file.txt ", function(data) {
// making processed data availabel app-wide
lists = processData(data);
// filling the 'suggestions list' the first time
suggestionListHtml(lists, $('.suggestions-container'))
});
// actions on input field input event
// only the third param differs in filterSpecies()
$('#species').on('input', function(e) {
const filteredList = filterSpecies($(this).val(), lists, 'label')
suggestionListHtml(filteredList, $('.suggestions-container'))
})
$('#identifiant').on('input', function(e) {
const filteredList = filterSpecies($(this).val(), lists, 'value')
suggestionListHtml(filteredList, $('.suggestions-container'))
})
// clicking on an item in the 'suggestions list' fills out the input fields
$('.suggestions-container').on('click', '.suggestion', function(e) {
const item = lists[$(this).attr('data-listindex')]
$('#species').val(item.label)
$('#identifiant').val(item.value)
})
});
function suggestionListHtml(filteredList, container) {
// creating HTML template for the 'suggestions list'
let html = ''
filteredList.forEach(item => {
html += `<span class="suggestion" data-listindex="${item.index}">label: ${item.label} - value: ${item.value}</span>`
})
// modifying the displayed 'suggestions list'
container
.empty()
.append(html)
}
// filtering the processed list
// #param substr - the text from the input field
// #param list - the list to be filtered
// #param attr - one of the keys in the processed list (label or value)
function filterSpecies(substr, list, attr) {
// doing the actual filtering
const filteredList = list.filter(item => {
return item[attr].toLowerCase().includes(substr.toLowerCase())
})
return filteredList
}
.suggestions-container span {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<div class="ui-widget">
<label for="species">Species: </label>
<input id="species">
<label for="identifiant">Identifiant: </label>
<input id="identifiant" style="width: 6em;">
</div>
<div class="suggestions-container">
</div>
</form>
I wrote a simple code to manipulate a query result to a bidimensional array (matrix) for a google chart datatable.
I'm getting nut on this strange array.push behaviour: when I push a new row in the matrix this method add the row, but also change the value of all the previus rows!
this is the code:
<script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script>
<script type="text/javascript">
query is the matrix from the query
var qry = [[{label:'Montly', type: 'string'}, {label:'Model', type: 'string'}, {label:'total', type: 'number'}], ['12-2017','California T',parseFloat(13+13)], ['12-2017','458 Speciale',parseFloat(3+2)], ['12-2017','GTC4Lusso',parseFloat(2+6)], ['12-2017','458 Spider',parseFloat(0+1)], ['12-2017','GTC4Lusso T',parseFloat(2+0)], ['12-2017','California',parseFloat(0+2)], ['12-2017','488 Spider',parseFloat(16+7)], ['12-2017','FF',parseFloat(1+3)], ['12-2017','488 GTB',parseFloat(17+10)], ['12-2017','F12berlinetta',parseFloat(3+4)], ['12-2017','458 Italia',parseFloat(0+3)], ['12-2017','F12tdf',parseFloat(1+21)], ['12-2017','LaFerrari Aperta',parseFloat(0+1)], ['12-2017','812 Superfast',parseFloat(2+3)], ['12-2017','Portofino',parseFloat(1+1)], ['11-2017','458 Spider',parseFloat(2+4)], ['11-2017','GTC4Lusso',parseFloat(4+34)], ['11-2017','California T',parseFloat(8+15)], ['11-2017','488 GTB',parseFloat(9+12)], ['11-2017','LaFerrari',parseFloat(0+1)], ['11-2017','458 Speciale',parseFloat(2+2)], ['11-2017','GTC4Lusso T',parseFloat(0+1)], ['11-2017','FF',parseFloat(1+4)], ['11-2017','812 Superfast',parseFloat(3+1)], ['11-2017','California',parseFloat(0+3)], ['11-2017','488 Spider',parseFloat(11+8)], ['11-2017','458 Italia',parseFloat(1+5)], ['11-2017','F12tdf',parseFloat(0+1)], ['11-2017','Portofino',parseFloat(0+1)], ['11-2017','F12berlinetta',parseFloat(3+4)], ['10-2017','458 Italia',parseFloat(0+4)], ['10-2017','California T',parseFloat(9+18)], ['10-2017','California',parseFloat(0+8)], ['10-2017','812 Superfast',parseFloat(1+2)], ['10-2017','F12tdf',parseFloat(1+2)], ['10-2017','Portofino',parseFloat(2+0)], ['10-2017','488 GTB',parseFloat(8+14)], ['10-2017','FF',parseFloat(0+3)], ['10-2017','458 Spider',parseFloat(1+3)], ['10-2017','LaFerrari Aperta',parseFloat(0+1)], ['10-2017','F12berlinetta',parseFloat(5+8)], ['10-2017','458 Speciale',parseFloat(3+2)], ['10-2017','488 Spider',parseFloat(9+7)], ['10-2017','GTC4Lusso',parseFloat(2+4)], ['9-2017','California',parseFloat(0+4)], ['9-2017','458 Speciale Aperta',parseFloat(1+0)], ['9-2017','FF',parseFloat(3+6)], ['9-2017','812 Superfast',parseFloat(2+1)], ['9-2017','458 Italia',parseFloat(1+3)], ['9-2017','GTC4Lusso',parseFloat(2+6)], ['9-2017','F12tdf',parseFloat(1+0)], ['9-2017','LaFerrari',parseFloat(0+1)], ['9-2017','488 GTB',parseFloat(18+13)], ['9-2017','458 Spider',parseFloat(3+2)], ['9-2017','F12berlinetta',parseFloat(4+10)], ['9-2017','458 Speciale',parseFloat(5+1)], ['9-2017','California T',parseFloat(23+37)], ['9-2017','488 Spider',parseFloat(11+14)], ['8-2017','FF',parseFloat(0+1)], ['8-2017','458 Spider',parseFloat(0+1)], ['8-2017','F12berlinetta',parseFloat(0+3)], ['8-2017','GTC4Lusso',parseFloat(0+6)], ['8-2017','488 GTB',parseFloat(0+1)], ['8-2017','California T',parseFloat(0+2)], ['8-2017','458 Italia',parseFloat(0+2)], ['8-2017','California',parseFloat(0+2)], ['7-2017','488 GTB',parseFloat(9+14)], ['7-2017','458 Speciale',parseFloat(1+4)], ['7-2017','California T',parseFloat(14+20)], ['7-2017','GTC4Lusso',parseFloat(1+7)], ['7-2017','California',parseFloat(1+6)], ['7-2017','458 Italia',parseFloat(1+4)], ['7-2017','458 Speciale Aperta',parseFloat(1+0)], ['7-2017','F12berlinetta',parseFloat(3+7)], ['7-2017','FF',parseFloat(2+3)], ['7-2017','458 Spider',parseFloat(1+3)], ['7-2017','488 Spider',parseFloat(10+10)], ['7-2017','F12tdf',parseFloat(0+1)], ['6-2017','488 GTB',parseFloat(7+23)], ['6-2017','458 Italia',parseFloat(2+2)], ['6-2017','812 Superfast',parseFloat(1+0)], ['6-2017','FF',parseFloat(0+3)], ['6-2017','GTC4Lusso',parseFloat(2+16)], ['6-2017','458 Spider',parseFloat(3+6)], ['6-2017','F12tdf',parseFloat(0+2)], ['6-2017','LaFerrari',parseFloat(0+1)], ['6-2017','F12berlinetta',parseFloat(4+9)], ['6-2017','458 Speciale',parseFloat(2+3)], ['6-2017','California T',parseFloat(16+18)], ['6-2017','488 Spider',parseFloat(11+14)], ['5-2017','458 Speciale',parseFloat(4+4)], ['5-2017','458 Italia',parseFloat(0+2)], ['5-2017','F12berlinetta',parseFloat(2+5)], ['5-2017','F12tdf',parseFloat(0+3)], ['5-2017','FF',parseFloat(0+1)], ['5-2017','488 GTB',parseFloat(8+13)], ['5-2017','458 Spider',parseFloat(0+2)], ['5-2017','GTC4Lusso',parseFloat(0+6)], ['5-2017','488 Spider',parseFloat(6+12)], ['5-2017','California T',parseFloat(12+19)], ];
I want to extract the unique models from the column and create the series for the line chart
function extractColumn(arr, column) {
function reduction(previousValue, currentValue) {
previousValue.push(currentValue[column]);
return previousValue;
}
return arr.reduce(reduction, []);
}
var prima = extractColumn(qry,1);
var models = [...new Set(prima)];
with extract column I get all the values of the column 1,and with the spread operator I get the models array, without duplicate.
var testa = []; // the header of the matrix
var tabella = []; // the array for the DataTable
for (i=1; i<models.length; i++) {
riga[i]=0;
}
testa.push([{label:'Montly', type: 'string'}]);
for (i=1; i<models.length; i++) {
testa.push([{label:models[i], type:'number'}]);
} // this code create the header objects
tabella.push(testa); //push the header into the tabella array
console.log(tabella[0]); //this push is ok!
var mese=qry[1][0]; // first date value
var riga = []; // array to add as row of the matrix
riga[0]=mese; // start populating the firs row
the following code should create a new row and append it to the chart table.
It checks the date of every row of the query matrix (qry): if it's a new date append the created row to the chart table and start a new row, if not it goes on creating the new row.
for (i=1; i<qry.length; i++){ // iterate qry array
if (mese!=qry[i][0]) { // check if the date is new
console.log(riga); // all the created row are ok!
tabella.push(riga); // append the row to the array
console.table(tabella); // WEIRD PROBLEM: ALL THE ARRAY ROW ARE CHANGED
mese=qry[i][0]; // EVERY CICLE!
for (x=1; x<models.length; x++) {
riga[x]=0;
}
riga[0]=mese; //reset the new row array and set the date
} else {
for (t=0;t<models.length;t++){
pos = models.indexOf(qry[i][1]);
if (pos != -1) {
riga[pos]=qry[i][2]
}
} // this code populates the new row.
}
}
The following code create the chart
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable(tabella);
var options = {
title: 'Montly Trend Focus on Complaint Type',
colors: ['#D9D904','#2A55FF','#000000'],
backgroundColor: {fill:'#FAFAFA',strokeWidth:1},
chartArea: {width:1800,height:380,left:70},
curveType: 'none',
legend: { position: 'bottom' },
hAxis: {direction:-1},
annotations: {
alwaysOutside: true,
textStyle: {
fontSize: 12,
bold: true,
color: '#000000',
}
}
};
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
chart.draw(data, options);
}
this is an image from my console:
I solved my problem redeclareing the new row in the loop:
for (i=1; i<qry.length; i++){
if (mese!=qry[i][0]) {
tabella.push(riga);
var riga=[]; // NEW DECLARATION
mese=qry[i][0];
for (x=1; x<models.length; x++) {
riga[x]=0;
}
riga[0]=mese;
}
else {
for (t=0;t<models.length;t++){
pos = models.indexOf(qry[i][1]);
if (pos != -1){riga[pos]=qry[i][2]}
}
}
}
Hope this will be usefull for others.
Is it possible to pass a function to the tooltip key in the Zingchart Json?
I tried the following so far:
$scope.applyTooltip = function (timestamp) {
console.log(timestamp);
var tooltip = "<div>";
var data = {
timestamp1: {
param1: "bla",
param2: "foo,
},
...
}
for(var param in data){
console.log(param);
tooltip += param+": "+data[param]+"<br>";
}
tooltop += "</div>;
return tooltip;
}
$scope.graphoptions = {
//...
//just displaying the relevant options
plot: {
"html-mode": true,
tooltip: $scope.applyTooltip("%kt"),
}
}
}
But the function gets the string "%kt" as it is and not the wanted X-Value of the hovered Plot. So how is it possible to pass the X-Value in the Function?
ZingChart does not allow passing in functions through the configuration object.
Instead, there is a property called "jsRule" which allows you to pass the name a function to be evaluated during each tooltip event.
tooltip : {
jsRule : "CustomFn.formatTooltip()"
}
Inside that function, a parameter will be available that will contain information about the node you moused over such as value, scaletext, plotindex, nodeindex, graphid, and more. Simply return an object for the tooltip (including the formatted text) and ZingChart will take care of the rest. Example provided down below.
The one caveat to jsRule is that the function name has to be accessible globally since ZingChart does not accept inline functions. We are aware of this issue and are planning for this to be an option in future versions.
CustomFn = {};
var myConfig = {
type: "line",
tooltip : {
jsRule : "CustomFn.formatTooltip()"
},
series : [
{
values : [1,3,2,3,4,5,4,3,2,1,2,3,4,5,4]
},
{
values : [6,7,8,7,6,7,8,9,8,7,8,7,8,9,8]
}
]
};
CustomFn.formatTooltip = function(p){
var dataset = zingchart.exec('myChart', 'getdata');
var series = dataset.graphset[p.graphindex].series;
var tooltipText = "";
for (var i=0; i < series.length; i++) {
tooltipText += "Series " + i + " : " + series[i].values[p.nodeindex] + "";
if (i !== series.length-1) {
tooltipText += "\n";
}
}
return {
text : tooltipText,
backgroundColor : "#222"
}
}
zingchart.render({
id : 'myChart',
data : myConfig,
height: 400,
width: 600
});
<!DOCTYPE html>
<html>
<head>
<script src= 'https://cdn.zingchart.com/2.3.1/zingchart.min.js'></script>
</head>
<body>
<div id='myChart'></div>
</body>
</html>