Reputation: 397
I am using stackByValue of amcharts to arranged a stack column chart . I would like to add a bullet point on each chart to check if they meet a certain target or not. Currently what happen is the bullet point is added to the stacked chart is there a way that I could do this without removing the stackByValue ?
Here is my JsFiddle: `http://jsfiddle.net/sky5rvdz/13/
$(document).ready(function() {
AmCharts.addInitHandler(function(chart) {
// Check if enabled
if (chart.valueAxes === undefined || chart.valueAxes.length === 0 || !chart.valueAxes[0].stackByValue)
return;
// Disable built-in stacking
chart.valueAxes[0].stackType = "none";
// Prepare all graphs
for (var i = 0; i < chart.graphs.length; i++) {
var graph = chart.graphs[i];
graph.originalValueField = graph.valueField;
graph.valueField = graph.originalValueField + "Close";
graph.openField = graph.originalValueField + "Open";
graph.clustered = false;
if (graph.labelText)
graph.labelText = graph.labelText.split("[[value]]").join("[[" + graph.originalValueField + "]]");
if (graph.balloonText)
graph.balloonText = graph.balloonText.split("[[value]]").join("[[" + graph.originalValueField + "]]");
}
// Go through each category and order values
for (var i = 0; i < chart.dataProvider.length; i++) {
// Assemble intermediate array of data point items
var dp = chart.dataProvider[i];
var items = [];
var sum = 0;
for (var x = 0; x < chart.graphs.length; x++) {
var graph = chart.graphs[x];
items.push({
"graph": graph,
"value": dp[graph.originalValueField]
});
}
var sortValue = 0;
// Order according to value
items.sort(function(a, b) {
if (sortValue == 0) {
return a.value - b.value;
} else {
return b.value - a.value;
}
});
// Calculate open and close fields
var offset = 0;
for (var x = 0; x < items.length; x++) {
var item = items[x];
dp[item.graph.openField] = offset;
dp[item.graph.valueField] = offset + dp[item.graph.originalValueField];
offset = dp[item.graph.valueField];
}
}
}, ["serial"]);
var response = [{
"name": "Jan",
"target": 2062186.74,
"USA": 0,
"MAN": 605873.95,
"PAN": 759763.5
}, {
"name": "Feb",
"target": 1492210.81,
"MAN": 499538.43,
"PAN": 559504.95,
"USA": 5850
}, {
"name": "Mar",
"target": 1455750,
"MAN": 403715.2,
"PAN": 694353.95,
"USA": 0
}, {
"name": "Apr",
"target": 2008623.96,
"USA": 0,
"MAN": 409993.3,
"PAN": 511030
}];
var graphs = Object.keys(response[0]).reduce(function(graphsArray, key) {
if (key !== "name" && key !== "target") {
graphsArray.push({
"balloonText": "<b>[[value]]</b>",
"balloonFunction": function(item, graph) {
var result = graph.balloonText;
for (var key in item.dataContext) {
if (item.dataContext.hasOwnProperty(key) && !isNaN(item.dataContext[key])) {
var formatted = AmCharts.formatNumber(item.dataContext[key], {
precision: chart.precision,
decimalSeparator: chart.decimalSeparator,
thousandsSeparator: chart.thousandsSeparator
}, 2);
result = result.replace("[[" + key + "]]", formatted);
}
}
return result;
},
"fillAlphas": 0.8,
"labelText": "[[title]]<br>",
"labelPosition": "middle",
"lineAlpha": 0.3,
"title": key,
"type": "column",
"color": "#000000",
//"showAllValueLabels": true,
"valueField": key
});
}
if (key === "target") {
graphsArray.push({
"balloonText": "<b>[[value]]</b>",
"balloonFunction": function(item, graph) {
var result = graph.balloonText;
for (var key in item.dataContext) {
if (item.dataContext.hasOwnProperty(key) && !isNaN(item.dataContext[key])) {
var formatted = AmCharts.formatNumber(item.dataContext[key], {
precision: chart.precision,
decimalSeparator: chart.decimalSeparator,
thousandsSeparator: chart.thousandsSeparator
}, 2);
result = result.replace("[[" + key + "]]", formatted);
}
}
return result;
},
"valueAxis": "v2",
"lineAlpha": 0,
"bullet": "round",
"bulletSize": 20,
"title": "target",
"type": "line",
"valueField": "target"
});
}
return graphsArray;
}, []);
var chart = AmCharts.makeChart("chartdiv", {
"type": "serial",
"theme": "light",
"legend": {
"horizontalGap": 10,
"maxColumns": 1,
"position": "right",
"useGraphSettings": true,
"markerSize": 10
},
"numberFormatter": {
"precision": 1,
"decimalSeparator": ".",
"thousandsSeparator": ","
},
"dataProvider": response,
"valueAxes": [{
"id": "v1",
"stackType": "regular",
/**
* A proprietary setting `stackByValue` which is not an
* official config option. It will be used by our custom
* plugin
*/
"stackByValue": true,
"axisAlpha": 0.3,
"gridAlpha": 0
}, , {
"id": "v2",
"axisAlpha": 0.3,
"gridAlpha": 0,
"position": "top",
"title": "Target"
}],
"gridAboveGraphs": true,
"startDuration": 0,
"graphs": graphs,
"categoryField": "name",
"categoryAxis": {
"gridPosition": "start",
"axisAlpha": 0,
"gridAlpha": 0,
"position": "left"
},
"export": {
"enabled": true
}
});
console.log(graphs);
console.log(response);
Object.keys(response[0]).forEach(key => {
console.log(key) // returns the keys in an object
// console.log(a[key]) // returns the appropriate value
})
});
Upvotes: 2
Views: 508
Reputation: 16012
The issue is that the sort by value plugin assumes that all graphs need to be sorted and modified to use the open/close fields to achieve this effect, which causes it to move your bullet to an incorrect location. Since you have multiple axes, you can modify the plugin to check if the graph belongs to the first axis and set a flag to be used to re-add the point correctly:
// Go through each category and order values
for (var i = 0; i < chart.dataProvider.length; i++) {
// ...
for (var x = 0; x < chart.graphs.length; x++) {
var graph = chart.graphs[x];
items.push({
"graph": graph,
// check if this graph's data points need to be omitted from the sorting process.
"ignoreSort": (graph.valueAxis && graph.valueAxis !== chart.valueAxes[0].id),
"value": dp[graph.originalValueField]
});
}
// ...
// Calculate open and close fields
var offset = 0;
for (var x = 0; x < items.length; x++) {
var item = items[x];
if (!item.ignoreSort) {
//process the pont as normal if it doesn't have the flag set with open/value fields
dp[item.graph.openField] = offset;
dp[item.graph.valueField] = offset + dp[item.graph.originalValueField];
offset = dp[item.graph.valueField];
} else {
//otherwise treat the point as a normal graph and use the value field
dp[item.graph.valueField] = dp[item.graph.originalValueField]
}
}
}
You'll also want to synchronize the axes' min/max values so that your target is correctly placed with respect to your stacked bars. You can achieve this using another custom plugin through addInitHandler
:
//synchronizes axes' min/max values using a custom synchronizeValueAxes property
//(while synchronizeGrid exists, it doesn't work with this particular chart)
AmCharts.addInitHandler(function(chart) {
if (chart.synchronizeValueAxes) {
setTimeout(function() {
var max = chart.valueAxes.reduce(function(max, axis) {
if (!isNaN(axis.max)) {
return Math.max(max, axis.max);
} else {
return max;
}
}, Number.MIN_VALUE);
var min = chart.valueAxes.reduce(function(min, axis) {
if (!isNaN(axis.min)) {
return Math.min(min, axis.min);
} else {
return min;
}
}, Number.MAX_VALUE);
chart.valueAxes.forEach(function(axis) {
axis.maximum = max;
axis.minimum = min;
axis.strictMinMax = true;
});
chart.validateData();
}, 500);
}
}, ["serial"]);
Upvotes: 1