Data from json file for use with CHAPS timeline - javascript

I'm trying to use the CHAP links library timeline (http://almende.github.io/chap-links-library/timeline.html).
Example17 is using JSON, but it's in the html file itself. I'd like to use an external JSON file sitting on the web server instead.
Here's my example.json:
{"timeline":[
{
"start":"2013,7,26",
"end":"2013,7,26",
"content": "Bleah1"
},
{
"start":"2013,7,26",
"end":"2013,8,2",
"content": "Bleah2"
},
{
"start":"2013,7,26",
"end":"2013,8,2",
"content": "Bleah3"
},
{
"start":"2013,7,26",
"end":"2013,8,2",
"content": "Bleah4"
}
]}
I added this:
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
And here's the modified function:
// Called when the Visualization API is loaded.
function drawVisualization() {
// Create a JSON data table
$.getJSON('example.json', function(jsondata) {
data = jsondata.timeline;
});
// specify options
var options = {
'width': '100%',
'height': '300px',
'editable': true, // enable dragging and editing events
'style': 'box'
};
// Instantiate our timeline object.
timeline = new links.Timeline(document.getElementById('mytimeline'));
function onRangeChanged(properties) {
document.getElementById('info').innerHTML += 'rangechanged ' +
properties.start + ' - ' + properties.end + '<br>';
}
// attach an event listener using the links events handler
links.events.addListener(timeline, 'rangechanged', onRangeChanged);
// Draw our timeline with the created data and options
timeline.draw(data, options);
}
Anyone who can tell me what I'm doing wrong gets a cookie! :-)
Update: I should specify that it's rendering the timeline div correctly, I'm just getting no data showing up.

Your start and end dates need to be parsed as Date objects for use in the timeline

I stumbled on this post as I was implementing similar functionality.
In version 2.6.1 of timeline.js, around line 3439 where the function links.Timeline.Item is declared, you'll notice a comment relating to implementing parseJSONDate.
/* TODO: use parseJSONDate as soon as it is tested and working (in two directions)
this.start = links.Timeline.parseJSONDate(data.start);
this.end = links.Timeline.parseJSONDate(data.end);
*/
I enabled the suggested code and it all works!* (go to the parseJSONDate function to see which formats are accepted)
*(works insofar as dates appear on the timeline.. I'm not using therefore not testing any selection/removal features, images, or anything like that..)

Related

Instead of creating new chart in ChartJS, the new updated chart keeps the old data and adds the new

I have been trying to solve this problem with ChartJS for a few days now, and I am completely stumped
My program shows the user a set of input elements they use to select data needing to be charted, plus a button that has an event to chart their data. The first chart works great. If they make a change to the data and click the button a second, third, or more time, all the data from the previous charts is plotted, PLUS their most recent selection.
It is behaving exactly like you might expect if the chart.destroy() object is not working, or perhaps would work if I created the chart object using a CONST (and could therefore add new data but not delete the beginning data).
I have tried all combinations of the browsers, chartjs and jquery libraries below:
Three different browsers:
• Chrome: Version 107.0.5304.121 (Official Build) (64-bit)
• Microsoft Edge: Version 107.0.1418.56 (Official build) (64-bit)
• Firefox: 107.0 64-bit
I have tried at least three different versions of Chart.js, including
• Versions 3.9.1
• 3.6.2
• 3.7.0
Jquery.js
• v3.6.1
• v1.11.1
Other things I have tried:
"use strict" (no luck)
In addition to destroying the chart object, removed the div containing the canvas, and appending it again.
using setTimeout() function before updating the chart after destroying it (because I thought maybe giving the destroy method more time might help)
type here
Software:
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/chart.js"></script>
<script type="text/javascript" src="js/dropdownLists.js"></script>
<script type="text/javascript" src="js/chartDataFunctions.js"></script>
<script type="text/javascript" src="js/chartJSFunctions.js"></script>
<body>
<div class = metadatasetup4" id = "buttons">
<button class="download" id="getchart" value="Get Chart">Chart</button>
<button class="download" id="downloadchart" value="Download">Download</button>
</div>
<div id = "bigchartdiv" class="bigchart">
<canvas id="myChart"></canvas>
</div>
</body>
<script>
$(window).on('load',function(){
//NOTE 1: In of my attempts to troubleshoot I tried strict mode (it didn't work)
//"use strict";
let data = {
labels: lbl,
datasets: [
]
};
let config = {
type: 'line',
data: data,
options: {
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
min:0,
pointStyle:'circle',
},
y1: {
type: 'linear',
display: true,
position: 'right',
suggestedMax: 25,
min: 0,
pointStyle: 'cross',
// grid line settings
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
},
},
}
}
};
// NOTE 2: The next line below, beginning with "var bigChartHTML =" was one of my later attempts to
// solve the problem. It didn't work, but my thought process was that if I removed
// the div containing the canvas, AND destroyed the chart object, that appending a "fresh"
// chart div to the body might be a work-around. This did not work.
var bigChartHTML = '<div id = "bigchartdiv" class="bigchart"><canvas id="myChart"></canvas></div>'
let ctx = document.getElementById('myChart').getContext('2d');
let bigChart = null;
// The getChartData() function below uses Ajax to populate various dropdown lists
// which enable the user to select the data is to be charted.
// There are no chartjs-related operations in getChartData()
getChartData();
$('#buttons').on('click','#getchart',function(){
if (bigChart!=null) {
//removeData(bigChart);
bigChart.destroy();
//bigChart = 1;
}
$("#bigchartdiv").empty(); //for this and next 2 lines, see NOTE 2 above
$("#bigchartdiv").remove();
$(bigChartHTML).insertAfter("#chartcontrols");
bigChart = new Chart(document.getElementById('myChart'),config);
//NOTE 3: I thought maybe bigChart.destroy() took time, so I tried
// using the setTimeout function to delay updating the chart
// (didn't work, but I left it in the code, anyway.)
setTimeout(function() {updateChart(bigChart)}, 2000);
//updateChart(bigChart);
});
// NOTE: The updateChart() function is actually included in "js/chartDataFunctions.js"
function updateChart(chart) {
/*
This section of the program reads the HTML elements then uses them
to make an Ajax request to sql server, and these become the
parameters for the newDataSet() function below.
*/
newDataset(chart,firstElement,newdataset,backgroundcolor,color);
}
// NOTE: The newDataSet() function is actually included in "js/chartJSFunctions.js"
// I show it here for brevity.
// It decides which axis (y or y1) to use to plot the datasets
// the dataset is pushed into the data, and chart.update() puts it in the chart object
function newDataset(chart,label,data,bgcolor='white',color='rgb(255,255,255)') {
var maxValue = Math.max(...data);
if (Number.isNaN(maxValue)) {
return;
}
if (maxValue == 0) {
return;
}
var axisID = 'y';
var ptStyle = 'circle';
//var pStyle = 'circle';
if (maxValue < 50) {
axisID = 'y1';
bgcolor = 'white';
//ptStyle = 'Star'
}
chart.data.datasets.push({
label:label,
yAxisID:axisID,
data:data,
borderColor:color,
backgroundColor:bgcolor,
//pointStyle:ptStyle
});
chart.update();
}
});
</script>
I found a work-around that solves my problem, but I still think this is a bug in ChartJS. Before calling bigChart.destroy(), I now do two things: First, reset the data object back to it's original value, and second, reset the config object back to it's original value, THEN call bigChart.destroy().
I think the destroy() method should handle that for me, but in my case, for whatever reason, it doesn't.
So, what I have is a work-around, not really a solution, but I'll take it.

Add custom parameter to info.contentsFunction

I need to be able to add some custom info to the pie.info.contentsFunction in Zoomcharts. I have multiple charts on the page, each one created like so...
var pc = new PieChart({
pie: {
innerRadius: 0.5,
},
container: chartContainer1,
area: { height: 500 },
data:chartData,
toolbar: {
"fullscreen": true,
"enabled": true
},
info: {
contentsFunction: boomChartTT
}
});
In the "boomChartTT" function I need to know what chart is being hovered upon. I'd like to be able to do something like this...
info: {
contentsFunction: boomChartTT(i)
}
...where 'i' is the index of the chart.
The reason I need to know the chart index is because I have some other data saved in an indexed array for each chart. The index of the chart matches the index of the data.
EXAMPLE: if user hovers on a slice in chart2 I'd want to pass '2' to the boomChartTT function so I can access the totals data for that chart (say, totalsData[2]).
I've done this in the past with other chart libraries by simply adding a data attribute to the chart container to give me the index like so...
<div id="chartContainer1" data-index="1"></div>
...and then I'm able to access the chartContainer from the hover function (contentsFunction) and then get that index.
I don't want to add the totals data to the actual chart data because I'd have to add it to each slice which is redundant.
Is there a way to do this?
Please let me know if my post is unclear.
EDITED TO ADD:
I don't think it matters but here is the boomChartTT function:
function boomChartTT(data,slice){
var tt="<div class=\"charttooltip\">";
if(data.name==="Others" || data.name==="Previous"){return tt+=data.name+"</div>";}
//var thisData=dataSearch(totalsData[i],"REFERRINGSITE",data.id);
tt+="<h5 class=\"strong\">"+data.id+"</h5>"+oHoverTable.render(thisData)+"</div>";
return tt;
}
The commented line is where I would need the index (i) to to get the correct totalsData.
SOLVED. I simply added "chartIndex" to the data like so...
for(var i=0;i<r.length;i++){
var thisDataObj ={
id:r[i].REFERRINGSITE,
value:r[i].PCTOFSALES,
name:r[i].REFERRINGSITE,
chartIndex: arguments[1],//<----- arguments[1] is the chart index
style: { expandable: false, fillColor: dataSearch(dataRSList,"REFERRINGSITE",r[i].REFERRINGSITE)[0].COLOR }
};
chartData.preloaded.subvalues.push(thisDataObj);
}
Then in the boomChartTT function...
function boomChartTT(data,slice){
var tt="<div class=\"charttooltip\">";
if(data.name==="Others" || data.name==="Previous"){return tt+=data.name+"</div>";}
var thisData=dataSearch(totalsData[data.chartIndex-1],"REFERRINGSITE",data.id);
tt+="<h5 class=\"strong\">"+data.id+"</h5>"+oHoverTable.render(thisData)+"</div>";
return tt;
}
I feared that adding custom fields to the chart data would break the chart (which I believe I've experienced with other libraries). So, there you go.

HighMaps with drilledDown

I am using highmaps in my application with drilledown enabled, it works fine for the sample data given for usa.
But it does not work with the data for srilanka. what could be the issue?
Here is my code:
$('#container').highcharts('Map', {
chart : {
events: {
drilldown: function (e) {
if (!e.seriesOptions) {
var chart = this,
mapKey = 'countries/lk/' + e.point.drilldown + '-all',
// Handle error, the timeout is cleared on success
fail = setTimeout(function () {
if (!Highcharts.maps[mapKey]) {
chart.showLoading('<i class="icon-frown"></i> Failed loading ' + e.point.name);
fail = setTimeout(function () {
chart.hideLoading();
}, 1000);
}
}, 3000);
// Show the spinner
chart.showLoading('<i class="icon-spinner icon-spin icon-3x"></i>'); // Font Awesome spinner
// Load the drilldown map
$.getScript('https://code.highcharts.com/mapdata/' + mapKey + '.js', function () {
data = Highcharts.geojson(Highcharts.maps[mapKey]);
// Set a non-random bogus value
$.each(data, function (i) {
this.value = i;
console.log(i);
});
// Hide loading and add series
chart.hideLoading();
clearTimeout(fail);
chart.addSeriesAsDrilldown(e.point, {
name: e.point.name,
data: data,
dataLabels: {
enabled: true,
format: '{point.name}'
}
});
});
}
this.setTitle(null, { text: e.point.name });
},
drillup: function () {
this.setTitle(null, { text: 'Sri Lanka' });
}
}
}
JSFiddle Link
The drilldown data doesn't exist in the Highmaps Map Collection for Sri Lanka. If you scroll down the page you can see that at the moment the only countries with data below country level (e.g. admin2 level) are Canada, Germany, France, Netherlands, Norway, USA.
When you click a district the fiddle is trying to retrieve data from a corresponding URL:
mapKey = 'countries/lk/' + e.point.drilldown + '-all',
and for Sri Lanka it is failing because the JS file for each district does not exist.
There is more info in the Highmaps docs about creating maps:
Highmaps loads its maps from GeoJSON, an open standard for description
of geographic features. Most GIS software supports this format as
export from for instance Shapefile or KML export. Read more in the API
reference and see the live demo.
There are three basic sources for your map:
Use our Map collection. Read the tutorial article on the map
collection to get started.
Find an SVG map online and convert it using
our (experimental) online converter.
Create your own map from scratch
using an SVG editor, then convert them online. Read our tutorial on
Custom maps for Highmaps.
and about the Highmaps Maps Collection:
For your convenience, Highmaps offers a free collection of maps,
optimized for use with Highmaps. For common maps, it saves you the
trouble of finding or drawing suitable SVG or GeoJSON maps.
For Admin2, we have only compiled selected countries, and these maps
are created from national files with their own license which is
specified on the SVG map and in the other format files as meta data.
If you miss your country, please contact us and we'll try to find a
suitable shapefile and generate more maps.

javascript passing variables to the next page (without php)

I am a beginner in html and javascript so your help will be much appreciated.
I am creating a page that accepts values x and y from a form and enters it into a graph.
The graph is working perfectly on the same web page but i would like to have the graph in an entirely different page after I click the submit button. How can i carry forward these variables to a different page and display the same graph? (I would like to avoid JQuery, php etc altogether as this is for a school project)
Here's my code:
function someFunction(){
var xScore = parseFloat(x)
var yScore = parseFloat(y)
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['x', 'y'],
[ xScore , yScore],
]);
var options = {
title: 'Sample Graph',
hAxis: {title: 'x value', minValue: 1, maxValue: 9},
vAxis: {title: 'y value', minValue: 1, maxValue: 9},
legend: 'none'
};
var chart = new google.visualization.ScatterChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
This chart loads when i place following the div in the body.
<div id="chart_div" style="width: 900px; height: 900px;">
I would like to place this div in the body of another html page but would like to carry forward the x and y values generated on this web page (they are generated when the form is submitted).
Use window.localStorage
Put the following literal object in some js file that you will refer to from the current and the next page (or just paste the object wherever necessary).
var xyStore =
{
GetX: function () {
return JSON.parse(localStorage.getItem("x"));
},
GetY: function () {
return JSON.parse(localStorage.getItem("y"));
},
SetX: function (x) {
localStorage.setItem("x", JSON.stringify(x));
},
SetY: function (y) {
localStorage.setItem("y", JSON.stringify(y));
},
};
use in current page:
xyStore.SetX(12);
use in next page:
alert(xyStore.GetX()); // alerts: 12
EDIT
Following #RokoC.Buljan comment, this functionality can be expanded as follows:
Get: function (name) {
return JSON.parse(localStorage.getItem(name));
},
Set: function (name, val) {
localStorage.setItem(name, JSON.stringify(val));
}
Use a simple form on the first page that is configured with method="GET" and action="/page2.html". The input fields within the form should also have a name attribute filled in, for example <input name="x">. Then when you press the submit button in your form, it will pass along the field names and values as a querystring to the second page. For example /graph.html?x=123&y=456.
On this second page you want to read these values, so you need to parse the querystring which is located in window.location.search. There are various ways of doing this, but no built-in feature (it will always be a string, for example x=123&y=456 that you need to parse). I will leave that open as you mention it is a school project and that last bit would be the next challenge ;)
(localStorage as described by #remdevtec is also an option of course.)

how to save page state and css to server? (Drawing app)

I have a web page where the user creates simple drawings using various blocks, e.g. shapes representing furniture are drag and dropped onto a building floor plan. It uses Interact.js.
The blocks themselves can be dragged/moved, resized, added, inserted, deleted, merged, split, recoloured, font etc by the user - JavaScript acting on HTML and CSS.
I plan to save changes locally (for offline if needed) and back to the server for sharing with others who have access to this project. Undo/redo is nice to have too.
How to save modified diagrams (html & CSS)?
Option 1:
document.getElementById('foo').innerHTML for the HTML.
For CSS you'd have to recursively traverse the whole DOM and match selectors on each element to the rules defined in each CSS file.
As described in the answer above, this will (most of the times) work for classes:
var classes = document.styleSheets[0].rules || document.styleSheets[0].cssRules;
for (var x = 0; x < classes.length; x++) {
if (classes[x].selectorText == className) {
(classes[x].cssText) ? alert(classes[x].cssText) : alert(classes[x].style.cssText);
}
}
But this is a bad, error-prone solution.
Option 2:
What you need to do is have a data model that you edit, think of JSON looking like this:
[
{type: 'circle', color: 'blue', x: 10, y: 15, children: [
{type: 'line', color: 'red', x: 100, y: 0, children: []}
]},
{type: 'square', color: 'greed', x: 100, y: 15, children: []}
]
Based on this you'd write a recursive function like this:
var foo = document.getElementById('foo'); // this is where you "draw" stuff
function draw(elements) {
var i;
for(i in elements) {
drawElement(elements[i]);
if(elements[i].children.length > 0) {
draw(elements[i].children);
}
}
}
function drawElement(element) {
var domElement = document.createElement("div");
domElement.className = 'element ' + element.type + ' ' + element.color;
domElement.style.left = element.x + 'px';
domElement.style.top = element.y + 'px';
foo.appendChild(domElement);
}
Now you need to define some CSS:
#foo {
position: relative;
}
#foo .element {
position: absolute;
}
#foo .element.square {
...
}
#foo .element.blue {
background-color: blue;
}
Next, the "interactive" part. Whenever your user adds something to the "canvas" instead of directly manipulating the DOM you only add stuff to that JSON tree and then delete the old DOM and run draw again.
You can go with option 1 but that will be a lot harder. Read the comments in the answer I attached, you'll see there are a lot of browser inconsistencies and limitations.
Option 3:
Working with a <canvas>, not the DOM is more manageable. Try looking into Fabric.js for example, it already handles "saving" and "initializing" from JSON and allows users to "draw" stuff to it.
With jQuery you can use .html() method to retrive inner html of your container. But for css I think you should manually examine all properties of all objects you want to save to get similar approach.
So, for both, if you can modify the code that handles drawing operations, I think the simplest way would be catalog all actions that user can do and store it in a variable that enable you to reproduce all the process another time.
For example:
[
["drawBox", 200, 200, 400, 400, "#ff0000", "#0000ff"],
...
]
This approach will be also useful if you want to impement undo/redo functionalities in the future.
You should store that 'state' in db.
you can use HTML5 SessionState to save rendered Page Content
also you can store that in local storage of browser and sync local storage with db.

Categories