JS object loop for dynamic div creation - javascript

I've got an object with some properties in which I want to display on a web page in a chart. Each object will have its own chart.
Currently receiving the objects and their properties but can't get them to display dynamically in a chart, this works with static data.
What I'm trying to achieve is to loop through the objects, create the chart and add the data to it dynamically, there isn't a constant of how many objects there will be.
IE - on page load I'll receive how many objects there are, 3 for example, as such, three boxes will be created, each with a chart inside. How do I achieve this?
I don't think there's an issue with the data I'm sending receiving, but more to do with how I'm creating an object and it's corresponding div with the pie chart inside.
Where am I going wrong?
Code:
<style type="text/css">
#box {
border: 1px solid;
width: 200px;
height: 200px;
}
#objectBox {
width: 100px;
height: 100px;
}
</style>
<script type="text/javascript">
function getData() {
$.ajax({
type: "GET",
url: "/Graphs/GetMyPie",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: drawChart
});
}
function drawChart(myObject) {
var dpoints = [];
// loop through array and create and display a pie for each object in array
for (var i = 0; i < myObject.length; i++) {
// each object
var prop1 = myObject[i].propertyOne;
var prop2 = myObject[i].propertyTwo;
var title = myObject[i].objectName;
// create container div and pie div
var outbox = document.createElement('div');
outbox.id = 'box';
document.body.appendChild('box');
var inbox = outbox.appendChild('objectBox' + i);
// fill pie div
dpoints[i] = [{ label: "Free", data: prop1, color: '#7DCC3D' }, { label: "Used", data: prop2, color: '#333366' }];
$.plot($('#objectBox' + [i]), dpoints[i], {
series: {
pie: {
show: true
}
}
});
}
$(document).ready(function () {
getData();
});
}
</script>

If you don't want to use jQuery, as it isn't much JavaScript, I have put together an example: https://jsfiddle.net/gg1kkqq7/2/
With the above example, put
var dpoints = [{ label: "Free", data: prop1, color: '#7DCC3D' }, { label: "Used", data: prop2, color: '#333366' }];
$.plot(objectBox, dpoints, {
series: {
pie: {
show: true
}
}
});`
below outbox.appendChild(inbox); which will then render whatever the data is.
On a side note, I believe your problem is with the var outbox = $('<div>', {id: 'box' + i});
var objectBox = $("<div>", { id: 'objectBox' + i }).appendTo(outbox);
$('body').append(outbox);

Try this:
function drawChart(myObject) {
// loop through array and create and display a pie for each object in array
for (var i = 0; i < myObject.length; i++) {
// each object
var prop1 = myObject[i].propertyOne;
var prop2 = myObject[i].propertyTwo;
// create container div and pie div
var outbox = $('<div>', {id: 'box' + i});
var objectBox = $("<div>", { id: 'objectBox' + i }).appendTo(outbox);
$('body').append(outbox);
// fill pie div
var dpoints = [{ label: "Free", data: prop1, color: '#7DCC3D' }, { label: "Used", data: prop2, color: '#333366' }];
$.plot(objectBox, dpoints, {
series: {
pie: {
show: true
}
}
});
}
}
I've changed it to use jQuery to create the containers. Your code there was all wrong -- the argument to appendChild is the element to append, not an ID. I couldn't see any reason for the dpoints array, so I just use a local dpoints variable.

Related

Sum of visible Points in ChartJs Bar Chart

the chartjs has the option to hide datasets by clicking on the label. I want to sum up all points of the barchart, but only the visible ones. I know how to sum up all points, but don't know how to check if the dataset is Visible or not. To sum up the points I use the onComplete animation event:
animation: {
onComplete: function(animation) {
var sqm = 0;
this.data.datasets.forEach(function (dataset) {
dataset.data.forEach(function (points) {
sqm = sqm + points;
})
})
$("#SquareMeterSurface").val(sqm);
}
},
here is how it looks like:
How can I do the sum for visible datasets only (in the graph above the blue ones are not visible)? I use ChartJs 2.8
thanks
So I found the solution by myself. In the 'animation' variable passed to the onComplete callback is an array 'animation.chart.active' which can be looped to find the active dataset Indexes. The active array is only populated when hovering over the bars of the graph, thats why the sum of points are only displayed when hovering over the bars.
The whole code looks now like this:
function success_(data) {
var ctx = document.getElementById('canvas').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: data,
options: {
title: {
display: true,
text: 'Square Meters done per day'
},
tooltips: {
mode: 'index',
intersect: false
},
maintainAspectRatio: false,
responsive: true,
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true
}]
},
animation: {
onComplete: function(animation) {
//alert('onAnimationComplete');
if (typeof animation.chart !== 'undefined' && typeof animation.chart.active !== 'undefined') {
var active_datasets = new Array();
//loop through active dataset to get the dataset indexes
//which are visible to the user
animation.chart.active.forEach(function(active_ds) {
active_datasets.push(active_ds._datasetIndex);
})
//loop through datasets to get the points for active datasets
var sqm = 0;
var i = 0;
this.data.datasets.forEach(function (dataset) {
if (active_datasets.indexOf(i) != -1) {
dataset.data.forEach(function (points) {
sqm = sqm + points;
})
}
i = i + 1;
})
$("#SquareMeterSurface").val(parseFloat(sqm).toFixed(1) + ' m2');
}
}
},
}
});
};
and the ajax call to invoce a .Net Core MVC action is like this:
$.ajax({
url: '/YourController/YourActionForGraph/',
type: 'GET',
data: {
'param1': $('#param1').val(),
'param2': $('#param2').val()
},
dataType: 'json',
success: success_,
error: function (request, error) {
alert("Request: " + JSON.stringify(request));
}
});
Thanks for your hint. This one helped me, so I came up with the following solution:
`const options = {
radius: "100%",
cutout: "90%",
animation: {
onComplete: function (animation) {
console.log(animation.chart.getDatasetMeta(0));
},
},
};`
You can call getDatasetMeta(index) on the chart object and you will get an object with all current data used to construct the chart. Here you find the total property with the current sum.
See: https://www.chartjs.org/docs/latest/developers/api.html#getdatasetmeta-index

Adding a highchart to a Leaflet Popup

I'm currently working on a project where the map I create displays all communities the akdatagateway tracks and when a marker is clicked a popup opens displaying a highchart of the population over time char along with some other data. I've gotten all markers to display and was able to get the desired data through the website's API. The problem lies in the generating the highchart in the popup. I cant figure out how to pass in or get the array to the onPopupOpen() to make the subsequent function calls.
Reverent Functions:
function addMarker(array){
for (i in array){
var communityData = array[i]
L.marker([array[i].latitude,array[i].longitude], {bounceOnAdd: true, bounceOnAddOptions: {duration: 250, height: 50}}).addTo(map).on('popupopen', onPopUpOpen)
.bindPopup('<p style="font-size:130%;"><b>'+ array[i].name +'</b></p><div id="container" style="min-width: 300px; height: 200px; margin: 0 auto"></div><p><br />PCE for Electricity: ' + pcePrice + '<br />EIA for Electricity: ' + eiaPrice + '</p>').addTo(community);
}
}
function getPopulationData(array){
var id = array.id
var years = ""
var populations = ""
var populationData = []
var community_pop = $.ajax({type: "GET", url: "/api/models/population/?community=" + id +"", async: false}).responseText
community_pop = JSON.parse(community_pop)
community_pop = community_pop.results
for ( i = 0; i < community_pop.length; i++){
years += "'" + community_pop[i].year + "',";
populations += community_pop[i].total_population +",";
}
populationData[0] = years
populationData[1] = populations
return populationData;
}
function onPopUpOpen (e) {
//some code to get the community id from popup to use the function.
// getPopulationData()
$('#container').highcharts({
chart: {
type: 'line'
},
title: {
text: 'Population Over Time'
},
subtitle: {
text: 'Source: AEDG'
},
xAxis: {
categories: [popData[0]]
},
yAxis: {
title: {
text: 'Population'
}
},
plotOptions: {
line: {
dataLabels: {
enabled: false
},
enableMouseTracking: true
}
},
series: [{
name: '',
data: [popData[1]]
}]
});
}
I've tested the functions on elements of the Community array and they do produce the correct data I just can't figure out how to make call them on the pop up and thus generate the highchart. Any commentary is appreciated!
Links to Reference info:
http://leafletjs.com/reference.html
http://www.highcharts.com/docs
It looks like your code should work, so without a full reproducible example, it's tough to troubleshoot. So, instead I'll offer a working example:
var popup = L.popup().setContent('<p style="font-size:130%;"><b>Some Name</b></p><div id="container" style="min-width: 300px; height: 200px; margin: 0 auto">Loading...</div>');
L.circle([51.49, -0.09], 500, {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5
}).addTo(map).bindPopup(popup);
map.on('popupopen', function(e) {
$.ajax({
type: "GET",
url: "data.json"
})
.done(function(data) {
$('#container').highcharts({
chart: {height: 175, width: 295},
title: {text: ''},
series: data
});
});
});
map.on('popupclose', function(e){
$('#container').html("Loading...");
});
Also, I'd warn you not to make async: false calls. Not only is it deprecated, but it can make for a poor user experience and it's totally unnecessary here.
Complete code sample is runnable here.

Update Highchart data form exported button

I'm trying to use the exporting option to add a button which is then used to switch between a line chart with the real point and another with the cumulative sum of them.
I'm using the following code:
$(function () {
$('#container').highcharts({
chart: {
type: 'line'
},
xAxis: {
tickPixelInterval: 200,
categories: jsonResponse["Date"]
},
series: {
data: jsonResponse["values"]
},
exporting: {
buttons: {
'myButton': {
_id: 'myButton',
symbol: 'diamond',
text: 'Cumulative',
x: -62,
symbolFill: '#B5C9DF',
hoverSymbolFill: '#779ABF',
onclick: function() {
if(!cumulative){
this.series[0].setData = cumcum(jsonResponse["values"]);
alert(this.series[1].setData);
cumulative = true;
} else {
this.series[0].setData = jsonResponse["values"];
cumulative = false;
alert(this.series[1].setData);
}
},
_titleKey: "myButtonTitle"
}
}
}
});
});
function cumcum(data){
var res = new Array();
res[0] = data[0];
for(var i=1; i<data.length; i++) {
res[i] = res[i-1] + data[i];
}
return res;
}
From the alert I can see that the data are correctly calculated but the plot stays the same.
I also tried series[0].yData and series[0].processedYData
setData is a function, you have to call it like:
this.series[0].setData(cumcum(jsonResponse["values"])
See API http://api.highcharts.com/highstock#Series for more information.

Highcharts - column chart redraw animation

I'm trying to update an existing data series with a new data array and invoke the redraw function when done. While this works perfectly, I'm not quite satisfied as I'd like to have a sort of grow/shrink transition. I have seen an example by Highcharts (fiddle around with the existing data set then click on the button "Set new data to selected series") but I can't replicate this behavior.
This is what code that I've written:
var series, newSeriesThreshold = this.chart.series.length * 2;
for (var i = 0; i < data.length; i += 2) {
series = {
name: this.data[i].title,
data: this.data[i].data,
color: this.data[i].color
};
if (i >= newSeriesThreshold) {
this.chart.addSeries(series, false);
} else {
var currentSeries = this.chart.series[i / 2];
currentSeries.setData(series.data, false);
}
}
this.chart.redraw();
These are the options when creating the chart:
var config = {
chart: {
renderTo: $(this.container).attr('id'),
type: this.settings.type,
animation: {
duration: 500,
easing: 'swing'
}
},
title: {
text: null
},
legend: {
enabled: this.settings.legend.show
},
tooltip: {
formatter: function() {
return this.x.toFixed(0) + ": <b>" + this.y.toString().toCurrency(0) + '</b>';
}
},
xAxis: {
title: {
text: this.settings.xaxis.title,
style: {
color: '#666'
}
}
},
yAxis: {
title: {
text: this.settings.yaxis.title,
style: {
color: '#666'
}
}
},
series: series,
plotOptions: {
column: {
color: '#FF7400'
}
},
credits: {
enabled: false
}
};
This yields an immediate update without transitioning effects. Any ideas what I might be doing wrong?
I have solved this problem by destroying and creating again the chart.
Here is the link on highcharts forum that helps me : http://forum.highcharts.com/highcharts-usage/animation-on-redraw-t8636/#p93199
The answer comes from the highcharts support team.
$(document).ready(function() {
var chartOptions = {
// Your chart options
series: [
{name: 'Serie 1' , color: '#303AFF', data: [1,1,1,1,1,1,1}
]
};
var chart = new Highcharts.Chart(chartOptions);
$("#my_button").click(function(){
chart.destroy();
chartOptions.series[0].data = [10,5,2,10,5,2,10];
chart = new Highcharts.Chart(chartOptions);
});
});
This remain a mystery. I managed to make the axis update which suggests that there is some kind of animation going on, but it's applied only to the axis, and not the columns.
In the end, I've settled with this behavior.
This might help:
var mychart = $("#mychart").highcharts();
var height = mychart.renderTo.clientHeight;
var width = mychart.renderTo.clientWidth;
mychart.setSize(width, height);
to update all charts
$(Highcharts.charts).each(function(i,chart){
var height = chart.renderTo.clientHeight;
var width = chart.renderTo.clientWidth;
chart.setSize(width, height);
});

javascript infovis toolkit: individal level distance for each level

how do i set individual levelDistance for each level in spacetree
when i set node.data.$width and label.style.width tree drawn with no equal edge
How to set levelDistance for each node level in the spacetree. For instance, I want to change the 'levelDistance' for node level 3. Thank you
function init(){
//init data
json = {
id: "node0",
name: "zvet",
data: {},
children: [{
id: "node0rrrrrr1",
name: "1.3",
data: {$orn:'left'},
children: [{
id: "fvwerr1",
name: "w33",
data: {$orn:'left'},
}]
},
{
id: "node02",
name: "qwe",
data: {$orn:'left'}
} ,{
id: "node03",
name: "bnm",
data: {$orn:'left'}
} ,{
id: "node04",
name: "1.3",
data: {$orn:"right",kk:"kk"}
},
{
id: "vwer",
name: "vfsewrg",
data: {$orn:"right",kk:"kk"}
},
{
id: "vweq33e",
name: "vvvserser",
data: {$orn:"right",kk:"kk"},
children: [{
id: "r345345",
name: "w33",
data: {$orn:'right'}
},
{
id: "u786786",
name: "w33",
data: {$orn:'right'}
},
{
id: "p809456",
name: "w33",
data: {$orn:'right'},
children: [{
id: "weqr232344",
name: "w33",
data: {$orn:'right',kk:"kk"},
children: [{
id: "weqoooooppp",
name: "w33",
data: {$orn:'right'}
}]
}]
}
]
}
]
};
//end
//init Spacetree
//Create a new ST instance
st = new $jit.ST({
//id of viz container element
injectInto: 'infovis',
Canvas:{
type: '2D'} ,
background:true,
//set duration for the animation
duration: 800,
//set animation transition type
transition: $jit.Trans.Quart.easeInOut,
//set distance between node and its children
levelDistance: 100,
levelsToShow:5,
multitree: true,
//enable panning
Navigation: {
enable:true,
panning:true
},
//set node and edge styles
//set overridable=true for styling individual
//nodes or edges
Node: {
height: 20,
width: 150,
type: 'rectangle',
color:'transparent',
overridable: true,
autoWidth:false ,
CanvasStyles: {
fillStyle: 'transparent'
}
},
Edge: {
type: 'bezier',
overridable: true
},
onBeforeCompute: function(node){
Log.write("loading " + node.name);
},
onAfterCompute: function(){
Log.write("done");
},
//This method is called on DOM label creation.
//Use this method to add event handlers and styles to
//your node.
onCreateLabel: function(label, node){
label.id = node.id;
label.innerHTML = node.name;
label.innerHTML='<div style="position:relative;">'+node.name+'</div>';
label.onclick = function(){
if(normal.checked) {
st.onClick(node.id);
//st.setRoot(node.id, 'animate');
st.selectedNodeId=node.id;
$("#"+node.id+" div").animate({"bottom":"+=10px"},"slow");
//st.addNodeInPath("1234");
} else {
st.setRoot(node.id, 'animate');
}
};
//set label styles
var style = label.style;
style.width = 150 + 'px';
style.height = 17 + 'px';
style.cursor = 'pointer';
style.color = '#fff';
style.backgroundColor = '#6257DD';
style.borderradius='14px';
style.boxshadow='0 0 16px #FFFFFF';
style.fontSize = '0.8em';
style.textAlign= 'center';
style.paddingTop = '3px';
if(node.data.kk=="kk")
{
style.width = 60+ 'px';
}
},
onPlaceLabel: function(label, node) {
} ,
//This method is called right before plotting
//a node. It's useful for changing an individual node
//style properties before plotting it.
//The data properties prefixed with a dollar
//sign will override the global node style properties.
onBeforePlotNode: function(node){
if(node.data.kk=="kk")
{
node.data.$width = 60;
}
//add some color to the nodes in the path between the
//root node and the selected node.
if (node.selected) {
node.data.$color = "#000000";
$("#"+node.id).css("background-color","red");
}
else {
delete node.data.$color;
$("#"+node.id).css("background-color","#6257DD");
//if the node belongs to the last plotted level
if(!node.anySubnode("exist")) {
//count children number
var count = 0;
node.eachSubnode(function(n) { count++; });
//assign a node color based on
//how many children it has
node.data.$color = ['#aaa', '#baa', '#caa', '#daa', '#eaa', '#faa'][count];
}
}
},
//This method is called right before plotting
//an edge. It's useful for changing an individual edge
//style properties before plotting it.
//Edge data proprties prefixed with a dollar sign will
//override the Edge global style properties.
onBeforePlotLine: function(adj){
if (adj.nodeFrom.selected && adj.nodeTo.selected) {
adj.data.$color = "#eed";
adj.data.$lineWidth = 3;
}
else {
delete adj.data.$color;
delete adj.data.$lineWidth;
}
}
});
//load json data
//end
//Add event handlers to switch spacetree orientation.
var top = $jit.id('r-top'),
left = $jit.id('r-left'),
bottom = $jit.id('r-bottom'),
right = $jit.id('r-right'),
normal = $jit.id('s-normal');
function changeHandler() {
if(this.checked) {
top.disabled = bottom.disabled = right.disabled = left.disabled = true;
st.switchPosition(this.value, "animate", {
onComplete: function(){
top.disabled = bottom.disabled = right.disabled = left.disabled = false;
}
});
}
};
st.loadJSON(json);
//compute node positions and layout
st.compute();
//optional: make a translation of the tree
st.geom.translate(new $jit.Complex(-200, 0), "current");
//emulate a click on the root node.
st.onClick(st.root);
st.select(st.root);
top.onchange = left.onchange = bottom.onchange = right.onchange = changeHandler;
//end
}
It is not possible to have different distances for different levels.
What you can do is to change the node.data.$size of the level, so it displays smaller or bigger than the other leaves.
Think the distance as an allocated space for the node to be placed in. If you create a node with a size smaller than the distance, you will get a gap, which can be seeing as a "border" (just visually) at the external part of it.

Categories

Resources