using datatable with amCharts - javascript

I am migrating google charts to amCharts. I am using a data array like this:
[
[CITY, SUM],
[A, 1500],
[B, 1470],
[C, 1920]
]
I can use this in google charts. So this solution is very flexible and dynamic. And I do not set any value field ot category field like amCharts.
But I see that amCharts data should be json object array.
[
{CITY: A, SUM: 1500},
{CITY: B, SUM: 1470},
{CITY: C, SUM: 1920}
]
So I need to know value ad category propery for every dataset.
var chart = AmCharts.makeChart("chartdiv", {
"categoryField": "CITY",
"graphs": [{
"type": "column",
"valueField": "SUM"
}]
}
SO this is not very flexible.
Is there any solution to get;
first item of json object is categoryField
second item of solution is valueField
Or using google datatable data in amCharts.

This functionality is not available out of the box as AmCharts requires this information to be defined upfront.
You can certainly write a pre-processing method or a plugin through AmCharts' addInitHandler method to convert your data and create graphs for you. Here's a basic example which defines a custom dataTable property containing the settings needed to make a custom plugin work:
//mini plugin to handle google datatable array of arrays format
AmCharts.addInitHandler(function(chart) {
if (!chart.dataTable && !chart.dataTable.data && !chart.dataTable.graph) {
return;
}
var dataProvider;
var graphs = [];
var graphTemplate = chart.dataTable.graph;
var fields = chart.dataTable.data[0];
var data = chart.dataTable.data.slice(1);
fields.slice(1).forEach(function(valueField) {
graphs.push({
type: graphTemplate.type || "line",
fillAlphas: graphTemplate.fillAlphas || 0,
lineAlpha: graphTemplate.lineAlpha || 1,
valueField: valueField
});
});
dataProvider = data.map(function(arr) {
var dataObj = {};
arr.forEach(function(value, idx) {
dataObj[fields[idx]] = value;
})
return dataObj;
});
chart.categoryField = fields[0];
chart.graphs = graphs;
chart.dataProvider = dataProvider;
});
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
//custom dataTable property used by the chart to accept dataTable format
"dataTable": {
"data": dataTable,
"graph": { //graph template for all value fields
"type": "column",
"fillAlphas": .8,
"lineAlpha": 1
}
}
});
You can extend this as much as you need.
Here's a demo using your data and an additional column of dummy data:
var dataTable = [
["CITY", "SUM", "AVG"],
["A", 1500, 500],
["B", 1470, 490],
["C", 1920, 640]
];
//mini plugin to handle google datatable array of arrays format
AmCharts.addInitHandler(function(chart) {
//check if the required properties for the plugin are defined before proceeding
if (!chart.dataTable && !chart.dataTable.data && !chart.dataTable.graph) {
return;
}
var dataProvider;
var graphs = [];
var graphTemplate = chart.dataTable.graph;
var fields = chart.dataTable.data[0];
var data = chart.dataTable.data.slice(1);
//create the graph objects using the graph template from the custom dataTable property
fields.slice(1).forEach(function(valueField) {
graphs.push({
type: graphTemplate.type || "line",
fillAlphas: graphTemplate.fillAlphas || 0,
lineAlpha: graphTemplate.lineAlpha || 1,
valueField: valueField
});
});
//construct the dataProvider array from the datatable data
dataProvider = data.map(function(arr) {
var dataObj = {};
arr.forEach(function(value, idx) {
dataObj[fields[idx]] = value;
})
return dataObj;
});
//update the chart properties
chart.categoryField = fields[0];
chart.graphs = graphs;
chart.dataProvider = dataProvider;
});
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
//custom dataTable property used by the chart to accept dataTable format
"dataTable": {
"data": dataTable,
"graph": { //graph template for all value fields
"type": "column",
"fillAlphas": .8,
"lineAlpha": 1
}
}
});
html,
body {
width: 100%;
height: 100%;
margin: 0px;
}
#chartdiv {
width: 100%;
height: 100%;
}
<script src="//www.amcharts.com/lib/3/amcharts.js"></script>
<script src="//www.amcharts.com/lib/3/serial.js"></script>
<script src="//www.amcharts.com/lib/3/themes/light.js"></script>
<div id="chartdiv"></div>

Related

How can I show all series in tooltip when series does not have same time values

I have a Chart that shows multiple time series. The different time series does not sample at the same time. Is there a way I can show all series in the tooltip? In the example, you can see that all series are included in the tooltip for the 2 first points as they are sampled at the same time. For the rest of the points, only 1 series is included.
var myChart = echarts.init(document.getElementById('main'));
var series = [{
"name": "sensor 1",
"data": [{
"value": [
"2019-02-20T11:47:44.000Z",
22.2
]
},
{
"value": [
"2019-02-20T12:03:02.000Z",
22.1
]
},
{
"value": [
"2019-02-20T12:18:19.000Z",
22.15
]
},
{
"value": [
"2019-02-20T12:33:36.000Z",
22.2
]
},
{
"value": [
"2019-02-20T12:48:53.000Z",
22.15
]
}
],
"type": "line"
},
{
"name": "sensor 2",
"data": [{
"value": [
"2019-02-20T11:47:44.000Z",
23.2
]
},
{
"value": [
"2019-02-20T12:03:02.000Z",
23.1
]
},
{
"value": [
"2019-02-20T12:22:19.000Z",
24.15
]
},
{
"value": [
"2019-02-20T12:39:36.000Z",
21.2
]
},
{
"value": [
"2019-02-20T12:52:53.000Z",
20.15
]
}
],
"type": "line"
}
]
var option = {
legend: {},
tooltip: {
trigger: 'axis',
},
xAxis: {
type: 'time'
},
yAxis: {
scale: true
},
series: series,
};
myChart.setOption(option);
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/4.0.4/echarts.min.js"></script>
<div id="main" style="width: 500px;height:400px;"></div>
Solution explanation
As stated in this feature request on echarts' github, they plan to add what you're looking for in the future. But for now, it is still not supported.
So I found a workaround to have the tooltip display all series even if they don't have a value at the exact x where the axisPointer is. To do so, I used the tooltip formatter that can be defined as a callback function that is called every time the tooltip has to be changed (i.e. every time the axisPointer moves on a new value) and where you can specify your own tooltip format.
Inside this function, you have access to every piece of information about the data at the axisPointer (especially its xAxis value in our case). Given the xAxis value of the axisPointer, we can go through our series and find the closest value from that xAxis value.
formatter : (params) => {
//The datetime where the axisPointer is
var xTime = new Date(params[0].axisValue)
//Create our custom tooltip and add to its top the dateTime where the axisPointer is
let tooltip = `<p>${xTime.toLocaleString()}</p> `;
//Go through each serie
series.forEach((serie, index) => {
//Find the closest value
value = serie.data.reduce((prev, curr) => Math.abs(new Date(curr.value[0]).valueOf() - xTime.valueOf()) < Math.abs(new Date(prev.value[0]).valueOf() - xTime.valueOf()) ? curr : prev).value[1]
/* Add a line in our custom tooltip */
// Add the colored circle at the begining of the line
tooltip += `<p><span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color: ${myChart.getVisual({ seriesIndex: index }, 'color')}"></span>`
// Add the serie's name and its value
tooltip += `${serie.name}    <b>${value}</b></p>`;
});
return tooltip;
}
Full code
Here is the full code, using your example :
var myChart = echarts.init(document.getElementById('main'));
var series = [{
"name": "sensor 1",
//step: "end",
"data": [{
"value": [
"2019-02-20T11:47:44.000Z",
22.2
]
},
{
"value": [
"2019-02-20T12:03:02.000Z",
22.1
]
},
{
"value": [
"2019-02-20T12:18:19.000Z",
22.15
]
},
{
"value": [
"2019-02-20T12:33:36.000Z",
22.2
]
},
{
"value": [
"2019-02-20T12:48:53.000Z",
22.15
]
}
],
"type": "line"
},
{
"name": "sensor 2",
//step: 'end',
"data": [{
"value": [
"2019-02-20T11:47:44.000Z",
23.2
]
},
{
"value": [
"2019-02-20T12:03:02.000Z",
23.1
]
},
{
"value": [
"2019-02-20T12:22:19.000Z",
24.15
]
},
{
"value": [
"2019-02-20T12:39:36.000Z",
21.2
]
},
{
"value": [
"2019-02-20T12:52:53.000Z",
20.15
]
}
],
"type": "line"
}
]
option = {
legend: {},
tooltip: {
trigger: 'axis',
formatter : (params) => {
//The datetime where the axisPointer is
var xTime = new Date(params[0].axisValue)
//Create our custom tooltip and add to its top the dateTime where the axisPointer is
let tooltip = `<p>${xTime.toLocaleString()}</p> `;
//Go through each serie
series.forEach((serie, index) => {
//Find the closest value
value = serie.data.reduce((prev, curr) => Math.abs(new Date(curr.value[0]).valueOf() - xTime.valueOf()) < Math.abs(new Date(prev.value[0]).valueOf() - xTime.valueOf()) ? curr : prev).value[1]
/* Add a line in our custom tooltip */
// Add the colored circle at the begining of the line
tooltip += `<p><span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color: ${myChart.getVisual({ seriesIndex: index }, 'color')}"></span>`
// Add the serie's name and its value
tooltip += `${serie.name}    <b>${value}</b></p>`;
});
return tooltip;
}
},
xAxis: {
type: 'time'
},
yAxis: {
scale: true
},
series: series,
};
myChart .setOption(option)
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.3.2/echarts.min.js"></script>
<div id="main" style="width: 600px; height:400px;"></div>
</body>
</html>
Further thoughts
Linear interpolation
Instead of displaying the value of the closest real point, we could also calculate the value with a simple linear interpolation.
Here is the formatter function with linear interpolation (not the most preformant, but working)
formatter : (params) => {
var xTime = new Date(params[0].axisValue)
let tooltip = `<p>${xTime.toLocaleString()}</p> `;
series.forEach((serie, index) => {
//Only works if series is chronologically sorted
prev_point = serie.data.reduce((prev, curr) => new Date(curr.value[0]).valueOf() <= xTime.valueOf() ? curr : prev)
next_point = serie.data.slice(0).reduce((prev, curr, i, arr) => {
if(new Date(curr.value[0]).valueOf() >= xTime.valueOf()) {
arr.splice(1);
}
return curr
})
var value = 0
if(next_point.value[1] == prev_point.value[1]){
value = next_point.value[1]
}
else {
//Linear interpolation
value = Math.round((prev_point.value[1] + (xTime.valueOf()/1000 - new Date(prev_point.value[0]).valueOf()/1000) * ((next_point.value[1] - prev_point.value[1]) / (new Date(next_point.value[0]).valueOf()/1000 - new Date(prev_point.value[0]).valueOf()/1000)))*10)/10
}
tooltip += `<p><span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color: ${myChart.getVisual({ seriesIndex: index }, 'color')}"></span> ${serie.name}    <b>${value}</b></p>`;
});
return tooltip;
}
Display the series as steps
To make it visually more accurate, you can display the series as
step: 'end' and get the closest previous value instead of just the
closest value, using:
value = serie.data.reduce((prev, curr) => new Date(curr.value[0]).valueOf() <= xTime.valueOf() ? curr : prev).value[1]
Doing so, the value displayed in the tooltip will be exactly what you see on the graph.
Performance
The reduce() methods are slow on large datasets as they go through the whole series. For example, replacing it with binary search (dichotomic search) will drastically improve the performance.
I'd be interested if anyone has an idea to make it even more performant.
Excellent !
One option that may interest you: you can get the color from the graph:
${myChart.getVisual({ seriesIndex: index }, 'color')}
This way you don't need to use the color array.

Dynamically change the radius of circle-based extrusion with Mapbox

I want to dynamically adjust the radius of circle-based extrusions with Mapbox based on the zoom level.
I have used for a toy dataset the solution provided by #stdob-- here
and for which the JS Fiddle is available here.
The problem with that solution is that it is computationally very expensive and with my real dataset (more than a million point) this is not a viable solution. I therefore thought about using queryRenderedFeatures() as suggested in the comments of the previous SO posts. However even that is not giving me a good enough interactive visualization.
Instead, I therefore wanted to initially load all of my dataset and layers (including the 3D extrusions) and then on map-zoom events only recompute the radius that is going to be used for the 3D extrusions.
Here is the code I used:
Here is simple geojson file to reproduce the error with
{"type": "FeatureCollection", "features": [{"id": 1, "type": "Feature", "properties": {"x": 1.0, "group": 1, "my_property": 217}, "geometry": {"type": "Point", "coordinates": [8.539961, 47.37347]}}, {"id": 2, "type": "Feature", "properties": {"x": 2.0, "group": 1, "my_property": 520}, "geometry": {"type": "Point", "coordinates": [8.517961, 47.37520]}}]}
the following code:
HTML:
<html>
<head>
<meta charset='utf-8' />
<title>Display buildings in 3D</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.48.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.48.0/mapbox-gl.css' rel='stylesheet' />
<script src='https://npmcdn.com/#turf/turf/turf.min.js'></script>
<script src="https://unpkg.com/supercluster#4.1.1/dist/supercluster.min.js"></script>
</head>
<body>
<div id='map'></div>
<script>
</script>
</body>
</html>
CSS:
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
JS:
mapboxgl.accessToken = 'pk.eyJ1IjoibG9ubmliZXNhbmNvbiIsImEiOiJjamxjaWNpOHQwMHV0M3FwaHhneGhvY2l2In0.7GxI8W_dnTKITNF4hEvZeQ';
var map = new mapboxgl.Map({
style: 'mapbox://styles/mapbox/light-v9',
center:[8.538961, 47.37247],
zoom: 10,
pitch: 20,
bearing: 0,
container: 'map'
});
var url = "REPLACE WITH GEOJSON LOCATION"
//
var zoom_level_3D_bars = 14
var radius_zoom_d = 10
var map_zoom = 10
map.on('load', function() {
// Insert the layer beneath any symbol layer.
var layers = map.getStyle().layers;
var labelLayerId;
for (var i = 0; i < layers.length; i++) {
if (layers[i].type === 'symbol' && layers[i].layout['text-field']) {
labelLayerId = layers[i].id;
break;
}
}
map.addSource("data", {
type: "geojson",
data: url,
});
map.addLayer({
'id': 'extrusion',
'type': 'fill-extrusion',
'minzoom': zoom_level_3D_bars,
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": []
}
},
'source': 'data',
'paint': {
'fill-extrusion-height': ['/', ['number', ['get', 'my_property'],0], 10],
'fill-extrusion-base': 0,
'fill-extrusion-opacity': 0.5
}
});
map.addLayer({
'id': 'population',
'type': 'circle',
'source': 'data',
'paint': {
'circle-color': {
'property': 'group',
'type': 'categorical',
stops: [
[1, 'rgba(252,141,98,1)'],
[2, 'rgba(102,194,165,1)'],
[3, 'rgba(102,194,165,1)'],
[4, 'rgba(102,194,165,1)'],
[5, 'rgba(102,194,165,1)'],
[6, 'rgba(102,194,165,1)'],
//'4', '#3bb2d0',
/* other 'rgba(102,194,165,0.1)'*/
]
},
}
});
map.on('data', function() {
//if (!firstTower) updateTower();
//});
//console.log("Initialize")
//initializeTower();
})
map.on('zoom', function() {
map_zoom = map.getZoom();
if(map.isSourceLoaded('data') == false){
return
}
if(map_zoom < zoom_level_3D_bars){
map.setPaintProperty('population', 'circle-radius', radius_zoom_d);
if(map.getPaintProperty('population','circle-opacity') != 1){
map.setPaintProperty('population', 'circle-opacity', 1)
}
}
radius_zoom_d = 10 - (map_zoom/2)
if(map_zoom >= zoom_level_3D_bars){
opacity_point = 0
console.log("Update tower bc zoom = "+map_zoom)
if(map.getPaintProperty('population','circle-opacity') != 0){
map.setPaintProperty('population', 'circle-opacity', 0)
}
updateTower();
}
})
function updateTower() {
var radiusPX = false;
var layer = map.getLayer('population')
if (layer.paint) radiusPX = map.getLayer('population').paint.get('circle-radius').evaluate();
if (radiusPX === false) return;
var data = {
"type": "FeatureCollection",
"features": []
}
//HERE IS THE PART where I would like to change the radius without having to take
// all the querySourceFeatures or queryRenderedFeatures for performance issues
//But I don't know how to just go through the dataset of the layer extrusion
}
map.on('data', function(e) {
// if (e.sourceId !== 'total') return
if (e.sourceId !== 'data') return
if (e.isSourceLoaded !== true) return
initializeTower()
})
//map.on('sourcedata', sourceCallback);
function initializeTower(){
if (layer.paint) radiusPX = map.getLayer('population').paint.get('circle-radius').evaluate();
if (radiusPX === false) return;
var nb_of_objects = 0
var data = {
"type": "FeatureCollection",
"features": []
}
map.querySourceFeatures('data').forEach(function(f) {
var object = turf.centerOfMass(f)
var center = object.geometry.coordinates
var xy = map.project(center)
xy.x += radiusPX;
var LL = map.unproject(xy)
LL = turf.point([LL.lng, LL.lat])
//var radius = turf.distance(center, LL, {
// units: 'meters'
//}) + 0.00000001
var radius = radius_zoom_d ;
var options = {
steps: 16,
units: 'meters',
properties: object.properties
};
data.features.push(turf.circle(center, radius, options))
nb_of_objects +=1
})
console.log("Finished preparing data for "+nb_of_objects+" objects")
map.getSource('extrusion').setData(data);
}
});
The first issue I have is that it triggers a ReferenceError: layer is not defined on the line if (layer.paint) radiusPX = map.getLayer('my_initial_2D_layer').paint.get('circle-radius').evaluate();. This is probably due to the layer's style not being rendered yet, but it seems from the documentation and few Mapbox Questions on SO and on their GitHub that there is no way to check for that.
If I comment this line, this triggers later on in the code a Cannot read property 'setData' of undefined on the line map.getSource('extrusion').setData(data); and also that it prints it processed 0 objects which is quite problematic. I get the output from my console.log().
Finished preparing data for 0 objects
The second issue that I have is that I don't know how I could later modify the data of this extrusion layer. It seems that there is no function to get the data for my extrusion layer in order to just change its radius (as it seems that this cannot be done dynamically in the layer style).
Would anyone know how to proceed?

multiple value axis with datetimes

I'm new in AmCharts.js. I want to create a chart with multiple value axis which represents occurences of prices on different websites for one product according to datetime (up to hours or better minutes) (not date).
So I need to draw chart with multiple lines which doesn't depends on each other. So when I one is null value, the value of second line is still drawn.
Every product can have different number of occurences so I can't hardcode colors and another properties of datasets.
One of the best approaches I found is AmStockChart because there can be drawn multiple lines. But there are multiple problems. One of them is that it needs to "compare" one line to another lines so if there is no value for datetime xxx, the value of line2 is not shown for this datetime.
The datetimes can differ (for one line is it 12.01 13:00, for another is it 14:00 etc).
This is my solution which doesn't work correctly since it has to be compared.
The JSON is: {'web_name':[[[year,month,day,hour...],price],[[[year,month....}
<script>
var lines = [];
var dataSets = [];
generateChartData();
function generateChartData() {
var google_chart_json = JSON;
var loopcounter = -1;
$.each(google_chart_json, function (key, val) {
var line = [];
loopcounter = loopcounter + 1;
$.each(val, function (_, scan) {
var year = scan[0][0];
var month = scan[0][1];
var day = scan[0][2];
var hour = scan[0][3];
var minute = scan[0][4];
var price = scan[1];
var data = {
'date': new Date(year, month - 1, day, hour, minute),
'value': price
};
line.push(data);
});
line.sort(function (lhs, rhs) {
return lhs.date.getTime() - rhs.date.getTime();
});
lines.push([key, line]);
});
console.log('LINES');
console.log(lines);
$.each(lines, function (_, name_line) {
var dict = {
'title': name_line[0],
"fieldMappings": [{
"fromField": "value",
"toField": "value"
}],
"dataProvider": name_line[1],
"categoryField": "date"
};
dataSets.push(dict);
});
}
console.log(dataSets)
var chart = AmCharts.makeChart("chartdiv", {
"allLabels": [
{
"text": "Free label",
"bold": true,
"x": 20,
"y": 20
}
],
categoryAxesSettings: {
minPeriod: "hh",//(at least that is not grouped)
groupToPeriods: ["DD", "WW", "MM"]//(Data will be grouped by day,week and month)
},
"type": "stock",
"theme": "light",
"dataSets": dataSets,
"panels": [{
"showCategoryAxis": false,
"title": "Value",
"percentHeight": 70,
"stockGraphs": [{
"id": "g1",
"valueField": "value",
"comparable": true,
"compareField": "value",
"balloonText": "[[date]][[title]]:<b>[[value]]</b>",
"compareGraphBalloonText": "[[title]]:<b>[[value]]</b>"
}],
"stockLegend": {
"periodValueTextComparing": "[[percents.value.close]]%",
"periodValueTextRegular": "[[value.close]]"
}
}],
{#https://docs.amcharts.com/javascriptcharts/ChartScrollbar#}
"chartScrollbarSettings": {
"graph": "g1",
"color": "#333333"
},
"chartCursorSettings": {
"valueBalloonsEnabled": true,
"fullWidth": true,
"cursorAlpha": 0.1,
"valueLineBalloonEnabled": true,
"valueLineEnabled": true,
"valueLineAlpha": 0.5
},
"periodSelector": {
"position": "left",
"periods": [{
"period": "MM",
"selected": true,
"count": 1,
"label": "1 month"
}, {
"period": "YYYY",
"count": 1,
"label": "1 year"
}, {
"period": "YTD",
"label": "YTD"
}, {
"period": "MAX",
"label": "MAX"
}]
},
"dataSetSelector": {
"position": "left",
},
"export": {
"enabled": true
}
});
chart.panelsSettings.recalculateToPercents = "never";
</script>
When I put the same datetimes for the values, it shows lines. But when each value has different datetime, it shows nothing except the first line:
Another solution (Line chart FiddleJS) has hardcoded lines which I can't do because there are different numbers of them. But the main problem is that they have own value axises.
Could you tell me what what to do in my code to achieve not compared multiple line chart with allowed different datetimes for different values and lines? Or if you know - recommend some type of amchart which can do this all?
The comparison requires that every date/time has to match or it won't show every point, as you noticed. In the AmCharts knowledge base, there's a demo that implements a mini-plugin that syncs the timestamps in your data prior to initializing the chart:
/**
* amCharts plugin: sync timestamps of the data sets
* ---------------
* Will work only if syncDataTimestamps is set to true in chart config
*/
AmCharts.addInitHandler(function(chart) {
// check if plugin is enabled
if (chart.syncDataTimestamps !== true)
return;
// go thorugh all data sets and collect all the different timestamps
var dates = {};
for (var i = 0; i < chart.dataSets.length; i++) {
var ds = chart.dataSets[i];
for (var x = 0; x < ds.dataProvider.length; x++) {
var date = ds.dataProvider[x][ds.categoryField];
if (dates[date.getTime()] === undefined)
dates[date.getTime()] = {};
dates[date.getTime()][i] = ds.dataProvider[x];
}
}
// iterate through data sets again and fill in the blanks
for (var i = 0; i < chart.dataSets.length; i++) {
var ds = chart.dataSets[i];
var dp = [];
for (var ts in dates) {
if (!dates.hasOwnProperty(ts))
continue;
var row = dates[ts];
if (row[i] === undefined) {
row[i] = {};
var d = new Date();
d.setTime(ts);
row[i][ds.categoryField] = d;
}
dp.push(row[i]);
}
dp.sort(function(a,b){
return new Date(a[ds.categoryField]) - new Date(b[ds.categoryField]);
});
ds.dataProvider = dp;
}
}, ["stock"]);
Just add this before your chart code and set the custom syncDataTimestamps property to true in the top level of your chart config and it will run upon initialization.

Chart.js : Is there a way to name each bubble in chart?

I have created a bubble chart using chart.js,which looks like the below
Is there a way to name each and every bubble in the chart? I am planning to put a data box below this chart. On clicking each bubble data box should display info associated with each bubble. Each bubble will have its own data like maturity_date,bond_type,credit_rating,symbol,etc... How can I name each bubble? These bubbles are created dynamically. This is the code I use to create the chart
$(document).ready(function(){
$.ajax({url: "xxxxxxxx.x.xx", success: function(result){
var dataObj = {};
dataObj.datasets = [];
var object = {};
object.label = 'First Dataset';
object.backgroundColor = [];
object.hoverBackgroundColor = [];
object.data = [];
var resultData = result.data;
var currentYear = new Date().getFullYear();
for (var i=0; i<resultData.length; i++) {
if(resultData[i].hasOwnProperty("maturity_date") && resultData[i].hasOwnProperty("ask_ytm")) {
var maturity_date = resultData[i].maturity_date.split("-");
var matYear = new Date(maturity_date[1]+"-"+maturity_date[0]+"-"+maturity_date[2]).getFullYear();
if (resultData[i].bond_type == "Tax Free" )
{
object.backgroundColor.push("#34A10C");
object.hoverBackgroundColor.push("#34A10C");
}
else
{
object.backgroundColor.push("#1130E8");
object.hoverBackgroundColor.push("#1130E8");
}
object.data.push({x: (matYear - currentYear), y: resultData[i].ask_ytm, r: 4});
}
}
dataObj.datasets.push(object);
var ctx = document.getElementById("myChart");
var myBubbleChart = new Chart(ctx,{
type: 'bubble',
data : dataObj,
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
}
});
}});
});
In your data declaration, you can add custom properties if you need to :
data: [{
x: 20,
y: 30,
r: 15,
symbol: "£",
bond_type: "corporate"
}, {
x: 40,
y: 10,
r: 10,
symbol: "$",
bond_type: "james"
} /* ... */]
Since this data is dynamic, you need to do it from your back-end of course.
Afterwards, you can access these new properties in your callback (onClick event for instance) :
options: {
onClick: function(e) {
var element = this.getElementAtEvent(e);
if (element.length > 0) {
var data = this.config.data.datasets[element[0]._datasetIndex].data[element[0]._index];
console.log(data);
// You can have the following for instance :
// data -> { x:40, y:10, r:10, symbol:"$", bond_type:"james" }
}
}
}

Looping through json array properties

I'm using JQuery, ChartJS, Moment.js gathering data in JSON format for multiple charts on the same page, but from the same JSON source. In the JSON the height objects are one graph and the lengths another one.
This is an example of how the JSON looks
"Series": {
"heights": [
{
"Date": "2014-10-01",
"Value": 22
},
{
"Date": "2014-10-01",
"Value": 53
},
{
"Date": "2014-10-01",
"Value": 57
},
],
"lengths": [
{
"Date": "2014-10-01",
"Value": 54
},
{
"Date": "2014-10-01",
"Value": 33
}
]
}
I've managed to loop through the JSON to display each graph but I'm not really able to do it using the "DRY - Don't repeat yourself" way. Which now I have large chunks of code that is hard to update/read.
$.getJSON("data.json", function(data) {
var dateArray = [];
var valueArray = [];
for ( var i = 0; i < data.Series["heights"].length; i++) {
var obj = data.Series.heights[i];
var date = obj["Date"].toString();
var Value = obj["Value"];
for ( var key in obj) {
//console.log(obj["Value"]);
//date = obj["Date"];
Value = obj[key].toString();
}
valueArray.push(Value);
dateArray.push(moment(date).format("MMMM Mo"));
var dataArray = {
labels: dateArray,
datasets: [
{
label: "Lengths",
strokeColor: "rgb(26, 188, 156)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: valueArray
}
]
};
}
var ctx = document.getElementById("lengthsChart").getContext("2d");
var myLineChart = new Chart(ctx).Line(dataArray, {
scaleShowGridLines : true,
bezierCurve : true,
bezierCurveTension : 0.4,
datasetStroke : false,
fillColor: "rgba(0,0,0,0)",
datasetFill : false,
responsive: true,
showTooltips: true,
animation: false
});
});
Right now I have this code in a switch statement for "heights", "lengths" etc... Which I guess is a horrible way to do it. But I've been unable to make a loop for the individual charts.
I've tried things like this:
for(var x in measurement) {
console.log(measurement[x]);
for ( var i = 0; i < data.Series.hasOwnProperty(measurement).length; i++) {
var obj = data.Series.hasOwnProperty(measurement)[i];
var date = obj["Date"].toString();
var Value = obj["Value"];
console.log(date, Value);
}
But I'm unable to get it to work, to loop through the data.Series. /heights/lengths../ [i]
I'm very thankful for tips how to accomplish this.
Thanks!
If you replace measurement with data.Series and get rid of the hasOwnProperty(measurement) thing, you are almost there. The only thing you need is a way to keep the transformation from a list of {Date, Value} objects to a pair of list of dates and value for each serie.
var series = {};
// This loop is looping across all the series.
// x will have all the series names (heights, lengths, etc.).
for (var x in data.Series) {
var dates = [];
var values = [];
// Loop across all the measurements for every serie.
for (var i = 0; i < data.Series[x].length; i++) {
var obj = data.Series[x][i];
// Assuming that all the different series (heights, lengths, etc.) have the same two Date, Value attributes.
dates.push(obj.Date);
values.push(obj.Value);
}
// Keep the list of dates and values by serie name.
series[x] = {
dates: dates,
values: values
};
}
series will contain this:
{
heights: {
dates: [
'2014-10-01',
'2014-10-01',
'2014-10-01'
],
values: [
22,
53,
57
]
},
lengths: {
dates: [
'2014-10-01',
'2014-10-01'
],
values: [
54,
33
]
}
}
So you can use them like this:
console.log(series);
console.log(series.heights);
console.log(series.heights.dates);
console.log(series.heights.values);
console.log(series.lengths);
console.log(series.lengths.dates);
console.log(series.lengths.values);

Categories