I'm building a dynamic line chart using Chart JS, and I've managed to get the function working that appends data labels to the end of the x-axis like this:
addData gif
What I cannot figure it out is how change the function so that instead of appending them to the end of the x-axis (per the gif), the function prepends the data labels to the front of the x-axis . Essentially when executed, the function adds the new data from the array to the beginning of the x-axis, not the end as per the current function.
Here's the code:
<button id="addData">Add Data</button>
document.getElementById('addData').addEventListener('click', function() {
if (myChart.data.datasets.length > 0) {
var year = YEARS[myChart.data.labels.length % YEARS.length];
myChart.data.labels.push(year);
myChart.data.datasets.forEach(function(dataset) {
dataset.data.push(myChart.data.datasets.data);
});
myChart.update();
}
});
The default labels on load.
var chartData = {
labels: ["09/10", "10/11", "11/12", "12/13", "13/14", "14/15", "15/16", "16/17", "17/18", "18/19"]
The additional labels to add to the x-axis, prior to the fist label on load which is "09/10".
var YEARS = ["08/09", "07/08", "06/07", "05/06, "04/05"];
I'm at a loss, so any help would be greatly appreciated!
Instead of using Array.push(), you should use Array.unshift() as follows:
myChart.data.labels.unshift(year);
myChart.data.datasets.forEach((dataset) => {
dataset.data.unshift(myChart.data.datasets.data);
});
Related
At the moment I have a simply Chartist line chart, and I can easily add an on click mechanism to it using:
chart.on('draw', function(data) {
if(data.type === 'line') {
data.element._node.onclick = function (){window.location = "https://www.google.com"}
}
});
However this adds the onclick to the whole line.
I wish to add the on click mechanism to each data point on the line chart instead.
For example, if my line chart data is:
[0,0], [1,1], [2,2]
I wish for there to be a separate on click mechanism for each x axis data point, if this makes sense.
You can add a click event for data.type = point
if (data.type === "point") {
data.element._node.onclick = function() {
window.location = "https://www.google.com/?q=" + data.value.y
}
}
Here's the jsFiddle link: https://jsfiddle.net/prerak6962/zv8t7o6p/12/
I have many CSV files,
I need create from them dataSetSelector
On select I need to get table
But im getting undefined on the end of my table.
And World map based on selected data
And bar chart on maps bottom
So need to get something like
Also screen must be automatically adapted by user screen size
And on mouse over each chart bar to highlight same value on map, the same on table items if possible
On press bar on chart select maps area with this value, the same on table items if possible
valueLegend min value must be min value from tables also as max value
If there are duplicates countries fields in table, then in table show only one country and highlight this row or put * to country name (and be good to show popup on mouse over with all duplicates values), on map put all duplicates into description section splitting by newline and show value with *
What I do
codepen.io
As I first day studing AmCharts, I cant get dataSetSelector and put data from multiple csv files into it. Also I cant find info how to join stock chart with map and table.
So please help to achieve my wysh.
I'll focus on AmCharts-specific stuff in 2-4 since that's the main point of the question. I am also only going to provide a solution for AmCharts version 3.x. You should be able to handle 1 and 5 from there as you can tweak the CSS to more accommodate 1 and add whatever logic you need to satisfy 5.
To get the easy parts out of the way first
dataSetSelector is a stock chart property. The only way to reproduce this functionality for maps and serial charts is to write your own <select> with <option> tags and JavaScript to trigger the desired load action on change.
Stock charts only allow for date-based data. It cannot be used for charts where the category/x axis is just a string like in your screenshot, so it is not an option for you.
Taking the above into account, you need to set and position your dropdown, table and chart/map divs and add code to link everything together.
Some basic HTML and CSS for this layout
HTML:
<div id="container">
<div id="selector-table-div">
<select id="data-selector">
<option value="path/to/csv-1">Dataset 1</option>
<option value="path/to/csv-2">Dataset 2</option>
</select>
<div id="datatable"></div>
</div>
<div id="mapdiv"></div>
<div id="chartdiv"></div>
</div>
CSS:
#mapdiv {
width: 70%;
height: 400px;
float: left;
}
#chartdiv {
width: 100%;
height: 250px;
}
#selector-table-div {
width: 20%;
height: 450px;
float: left;
}
You're on your own for making this more responsive for height. I omitted the datatable stuff and highlighted row for brevity.
In your JS, you'll want to attach a change event to trigger a page update when a different dropdown item is selected:
document
.getElementById("data-selector")
.addEventListener("change", function(e) {
updatePage(e.target.value); //update page calls AmCharts.loadFile, updates/creates the table, map and chart with new data
});
Since you're planning on using both charts and maps on the same page, you need to use amcharts.js, and ammap_amcharts_extension.js. Using amcharts.js and ammaps.js in the same page will cause bugs with both your charts and maps as both files override each other's methods. Your column chart will need serial.js:
<script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="https://www.amcharts.com/lib/3/ammap_amcharts_extension.js"></script>
<script src="https://www.amcharts.com/lib/3/serial.js"></script>
<!-- other stuff omitted -->
Since you want your map to be tied to your data, you'll want to provide an easy way to map each row to a map area. Adding ISO 3166-2 country codes to your CSVs will simplify the process immensely:
country,visits,country_code
USA,2025,US
China,1882,CN
Japan,1809,JP
...
From there, you can set up your map's areas using your newly created country_code column as the MapArea id to activate the area on the map:
var areas = data.map(function(row) {
return {
id: row.country_code, //use ISO code for area ids
value: row.visits
};
});
// ...
AmCharts.makeChart("mapdiv", {
// ..
dataProvider: {
map: "worldLow",
areas: areas
}
});
To capture the min/max and assign it to the area, simply loop through the data and use Math.min/Math.max:
var minValue = Number.MAX_VALUE;
var maxValue = Number.MIN_VALUE;
data.forEach(function(row) {
minValue = Math.min(minValue, row.visits);
maxValue = Math.max(maxValue, row.visits);
});
// ...
AmCharts.makeChart("mapdiv", {
// ..
valueLegend: {
minValue: minValue,
maxValue: maxValue
// ...
}
});
You'll also want to adjust your map/chart creation code in separate functions that know when to either create a new map/chart or update an existing one:
var map, chart;
// ...
function updateMap(data) {
// ...
if (map) {
//makeChart here
}
else {
map.dataProvider.areas = areas;
map.valueLegend.minValue = minValue;
map.valueLegend.maxValue = maxValue;
map.validateData(); // update map
}
For the map, you'll also want to make sure that the map label placement code is called not only upon init, but also when the map is updated:
function updateMap(data) {
// ...
if (map) {
//makeChart here
}
else {
// data update here
}
updateLabel(); //update labels - same code as before
Creating your chart is pretty straightforward. You can add a clickGraphItem and rollOverGraphItem event to select the corresponding map area and highlight the table row on click/hover:
chart = AmCharts.makeChart("chartdiv", {
type: "serial",
dataProvider: data,
// ...
listeners: [
{
event: "clickGraphItem",
method: handleBarInteraction
}, {
event: "rollOverGraphItem",
method: handleBarInteraction
}
]
function handleBarInteraction(e) {
map.selectObject(map.getObjectById(e.item.dataContext.country_code));
var selected = document.querySelector(".selected");
if (selected) {
selected.classList.remove("selected");
}
document
.getElementById(e.item.dataContext.country_code)
.classList.add("selected");
}
Your undefined line is likely coming from an extra newline in your CSV. You can simply check the last item and pop it out of the array before creating your table, map and chart:
var data = AmCharts.parseCSV(response, {
// ...
});
if (data[data.length -1].country === undefined) {
data.pop();
}
Here's a complete codepen with all of the above plus some restructured code. Note that the labels are placed in weird places. The example you pulled the label code from defines exception latitude and longitude variables for you to set up for specific areas. You'll need to figure out those values.
I need to create a varying amount of the same morris js graphs depending on the data I pull from the database, i.e everything will stay the same except form the actual data. My code works perfectly for one graph but when I try and loop through an array to make new graphs it all messes up, any ides how to fix this?
Here is my code, I have just hardcoded values as I still need to work out how to automatically create variable and add them to an array:
<script>
var jsonVMs= [{"y":"2015-03-10 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-11 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-12 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-13 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-14 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-15 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-16 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-17 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-18 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-19 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-20 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-21 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-22 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-23 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-24 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-25 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-26 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-27 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-28 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-29 00:00:00","a":"20.00","b":"0.0000000"},{"y":"2015-03-30 00:00:00","a":"20.00","b":"0.0000000"}];
var jsonVMs1= [{"y":"2015-03-11 00:00:00","a":"3","b":"3"},{"y":"2015-03-12 00:00:00","a":"5","b":"1"},{"y":"2015-03-13 00:00:00","a":"4","b":"0"},{"y":"2015-03-14 00:00:00","a":"4","b":"0"},{"y":"2015-03-15 00:00:00","a":"4","b":"0"},{"y":"2015-03-16 00:00:00","a":"6","b":"1"},{"y":"2015-03-17 00:00:00","a":"12","b":"5"},{"y":"2015-03-18 00:00:00","a":"14","b":"5"},{"y":"2015-03-19 00:00:00","a":"14","b":"2"},{"y":"2015-03-20 00:00:00","a":"14","b":"3"},{"y":"2015-03-21 00:00:00","a":"15","b":"2"},{"y":"2015-03-22 00:00:00","a":"15","b":"2"},{"y":"2015-03-23 00:00:00","a":"15","b":"4"},{"y":"2015-03-24 00:00:00","a":"17","b":"6"},{"y":"2015-03-25 00:00:00","a":"17","b":"6"},{"y":"2015-03-26 00:00:00","a":"19","b":"9"},{"y":"2015-03-27 00:00:00","a":"17","b":"6"},{"y":"2015-03-28 00:00:00","a":"17","b":"6"},{"y":"2015-03-29 00:00:00","a":"17","b":"6"},{"y":"2015-03-30 00:00:00","a":"18","b":"6"}];
var a = [jsonVMs,jsonVMs1];
</script>
<div id="VMsDiv1" ></div>
<script type="text/javascript">
var index =0;
while (index < a.length) {
new Morris.Line({
// ID of the element in which to draw the chart.
element: 'VMsDiv1',
// Chart data records -- each entry in this array corresponds to a point on
// the chart.
data:a[index],
// The name of the data record attribute that contains x-values.
xkey: 'y',
// A list of names of data record attributes that contain y-values.
ykeys: ['a','b'],
// Labels for the ykeys -- will be displayed when you hover over the
// chart.
xLabelFormat: function(d) {
return d.getDate()+'/'+(d.getMonth()+1)+'/'+d.getFullYear();},
labels: ['Total VMs','Powered On'],
dateFormat: function(date) {
d = new Date(date);
return d.getDate()+'/'+(d.getMonth()+1)+'/'+d.getFullYear();
},
hideHover: true
});
index++
}
As per my comment, you need to separate the div where the charts will be rendered.
As such, add another div like so:
<div id="VMsDiv0" ></div>
And change the line to this:
while (index < a.length) {
new Morris.Line({
// ...
element: 'VMsDiv'+index,
// ...
});
}
I got a HighChart with several 2 series.
Now I want certain points in series 1 to be disabled when I click a link.
This is my code so far:
$('.remove').click(function () {
var chart = $('#plot').highcharts(),
series = chart.series[0];
if (series.data.length) {
chart.series[0].data[0].remove();
}
});
The problem is, that after removing this point, [0] changes to another value and after clicking it again, it goes on and on with deleting.
I just want the points to disappear, this is possible with visible:
visible Since 1.2.0 Read only. The series' visibility state as set by
series.show(), series.hide(), or the initial configuration.
But I just don't manage to implement it the right way in my onClick event.
If I understand you well, you need to keep "place" where the point was? If yes, you can try to use point.update() function and set null value.
Example: http://jsfiddle.net/gd4q4jo0/1/
I solved clicks on a link to delete points like this:
$('.remove').click(function () {
var series = chart.series[0];
var id = $(this).data('id');
if (series.data.length) {
// disable point in graph
chart.series[0].data[id-1].update(
{
y:null
});
}
// delete used tablerow
$(this).closest("tr").fadeOut(50);
});
And I managed to expulse points onClick on the graph with an event, it's working like this:
series: [{
name: 'time',
data: data: [[1, 129386],[2, 123966],[3, 123162],[4, 123245],[5, 124314],[6, 123946],[7, 124156],[8, 123367],[9, 124460],[10, 123366],[11, 123182],[12, 123915],[13, 124627],[14, 123142],[15, 124044],[16, 124346],[17, 123156],[18, 124356],[19, 123511],[20, 124239],[21, 123252],[22, 125169],[23, 125027],[24, 123508],[25, 124065],[26, 122719],[27, 124199],[28, 122968],[29, 124132],[30, 124052],[31, 124383],[32, 123265],[33, 124083],[34, 123855],[35, 124284],[36, 123719],[37, 123213],[38, 124245],[39, 123079],[40, 123721]],
events: {
// if point gets clicked, it'll be deleted
click: function(event) {
console.log(event.point);
var pointId = event.point.x;
$("#hidden-points-table").fadeIn(1000);
$('#hidden-elements').append(pointId + ", ");
event.point.update(
{
y: null
});
// deleting the table row
$("[data-id='"+pointId+"']").closest("tr").fadeOut(50);
}
}
}
Since it was hard to find solutions, I hope this will help some people with it.
This page was really helpful, too.
I am trying to get my d3.js line chart reloading when the user chooses an item in a dropdown menu, with the data corresponding to this item.
My menu is a list of stock market values:
YHOO
FB
...
For each of these, I have a JSON file with the data.
The graph in itself is working.
I put the code in a [JSFiddle], which doesn't work because it is supposed to use d3 and knockout.js.
It may be easier to work from this Github Gist.
Anyway, the code past line 83 changes newValue for each choice in the dropdown.
The data is stored in yahoo.json and fb.json.
How can I have the graph reloading each time the user selects a new choice in the dropdown menu with the data associated with this choice?
Thank you SO MUCH.
EDIT: temporary hack
/*User's stock choice*/
var viewModel = {
choice: ["AAPL", "YHOO", "FB", "MSFT"],
selectedChoice: ko.observable("two"), /*Knockout.js is used for having an observable array*/
};
viewModel.selectedChoice.subscribe(function(newValue) {
console.log(newValue);
if (newValue === "YHOO") {
d3.select("svg").remove();
d3.json('yahoo.json', draw);
} else if (newValue === "FB") {
d3.select("svg").remove();
d3.json('fb.json', draw);
}
});
ko.applyBindings(viewModel);
You can actually use d3 to bind events to the dropdown menu and then call a function when an on change event occurs. The function would go off and, in your case, grab the stock values from Yahoo. The only real trick is getting the data out of this. I ended up console logging this and digging through until I found __data__. Using d3 means you don't need knockout for this and can remove that code from your example.
Anyway, to set this up you'll need a div to append the dropdown menu and a set of stock names. You can use this list of stock names to create the dropdown menu as shown below.
var names = ["AAPL", "YHOO", "FB", "MSFT"];
d3.select("#dropDown")
.append("select")
.on("change", function() {
change(this.options[this.selectedIndex].__data__);
})
.selectAll("option")
.data(names).enter()
.append("option")
.text(function(d) {
return d;
});
Of course you need to setup the change function, which needs to wrap up all the variables required to do the call to Yahoo. Obviously, you'll need to pass in the stock parameter and once you've received the json back from Yahoo you'll need to call your draw function. So borrowing from your code something like this should work:
function change(stock) {
var url = 'http://query.yahooapis.com/v1/public/yql';
var startDate = '2013-09-06';
var endDate = '2014-03-06';
// NB the inclusion of the stock parameter
var req = encodeURIComponent('select * from yahoo.finance.historicaldata where symbol in ("' + stock + '") and startDate = "' + startDate + '" and endDate = "' + endDate + '"');
var data = $.getJSON(url, 'q=' + req + "&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json", draw);
}
You'd have to call the draw function on load otherwise you'll end up with a blank screen and also sort out transitioning from one data set to the next.