Amcharts4: 'legendSettings' 'itemValueText' to check for empty or undefined value - javascript

So, I have created a percentage column chart where on bar hover, I am showing the data in tooltip and in legend similar to https://www.amcharts.com/docs/v4/concepts/legend/#Interacting_with_cursor.
Also I have added % in the end. (I am getting data like 40, 55, 90 which means 40%, 50% and 90% respectively from backend API not like 0.4 or 0.5).
I am displaying the average percentage in the starting also.
series.legendSettings.itemValueText = "[bold]{valueY}%[/]";
series.legendSettings.valueText = "(Avg: [bold]{valueY.average.formatNumber('#.##')}%[/])";
So in some case for whole data-set one of the column is null or undefined, I am not getting the key itself and this leads to legend display only % . On hover it will display %.
So my question is - Is there some way where we can check 'itemValueText' or {valueY} to check whether it is 'undefined' and not add % in the end ?
Or is there any other way and I am doing it wrong?
Basically something like -
series.legendSettings.itemValueText = "{valueY}";
if (series.legendSettings.itemValueText !== undefined) { // or check {valueY}
series.legendSettings.itemValueText = "[bold]{valueY}%[/]"; // Only if value is undefined add % in last
series.legendSettings.valueText = "(Avg: [bold]{valueY.average.formatNumber('#.##')}%[/])";
}

You can create a textOutput adapter on the legend's valueLabels template to check the final text output based on your template string and adjust accordingly.
chart.legend.valueLabels.template.adapter.add("textOutput", function(text, target) {
if (text === '(Avg: [bold]%[/])') {
return 'N/A';
}
else if (text === '[bold]%[/]') {
return '';
}
return text;
});
Demo

Related

javascript/amcharts - easy way to use legend to show/hide columns of 1 graph for Amchart Column Chart

I can't seem to find an easy way to add legend which has switchable functionality for items in a single graph for amcharts. I searched around and found a column chart which has switchable graphs (JSFiddle 1). I found switchable items legend but it doesn't resize properly (JSFiddle 2).
This is the closest I can find to adding legends from multiple items of single graph (CodePen 1). It is from amchart website itself but there is no switchable functionality. How can I add the switchable functionality here which allows column resizing (ie. 2 items will be shown with bigger column than 10 columns)? I tried this initially to just see if switch functionality can be added but it does not work:
AmCharts.addInitHandler(function(chart) {
//check if legend is enabled and custom generateFromData property
//is set before running
if (!chart.legend || !chart.legend.enabled || !chart.legend.generateFromData) {
return;
}
var categoryField = chart.categoryField;
var colorField = chart.graphs[0].lineColorField || chart.graphs[0].fillColorsField;
var legendData = chart.dataProvider.map(function(data) {
var markerData = {
"title": data[categoryField] + ": " + data[chart.graphs[0].valueField],
"color": data[colorField]
};
if (!markerData.color) {
markerData.color = chart.graphs[0].lineColor;
}
return markerData;
});
chart.legend.data = legendData;
// here is the code I add
chart.legend.switchable=true;
}
Update - The AmCharts knowledge base demo has been updated to include the modifications below.
In order to resize the chart outright, you have to actually modify the dataProvider and remove the element from the array and redraw the chart. You can use the legend's clickMarker to store the data item into the event dataItem object and retrieve it as needed through the hidden flag. Combining the fiddles from previous solutions together, I came up with this:
/*
Plugin to generate legend markers based on category
and fillColor/lineColor field from the chart data by using
the legend's custom data array. Also allows for toggling markers
and completely removing/adding columns from the chart
The plugin assumes there is only one graph object.
*/
AmCharts.addInitHandler(function(chart) {
//method to handle removing/adding columns when the marker is toggled
function handleCustomMarkerToggle(legendEvent) {
var dataProvider = legendEvent.chart.dataProvider;
var itemIndex; //store the location of the removed item
//Set a custom flag so that the dataUpdated event doesn't fire infinitely, in case you have
//a dataUpdated event of your own
legendEvent.chart.toggleLegend = true;
// The following toggles the markers on and off.
// The only way to "hide" a column and reserved space on the axis is to remove it
// completely from the dataProvider. You'll want to use the hidden flag as a means
// to store/retrieve the object as needed and then sort it back to its original location
// on the chart using the dataIdx property in the init handler
if (undefined !== legendEvent.dataItem.hidden && legendEvent.dataItem.hidden) {
legendEvent.dataItem.hidden = false;
dataProvider.push(legendEvent.dataItem.storedObj);
legendEvent.dataItem.storedObj = undefined;
//re-sort the array by dataIdx so it comes back in the right order.
dataProvider.sort(function(lhs, rhs) {
return lhs.dataIdx - rhs.dataIdx;
});
} else {
// toggle the marker off
legendEvent.dataItem.hidden = true;
//get the index of the data item from the data provider, using the
//dataIdx property.
for (var i = 0; i < dataProvider.length; ++i) {
if (dataProvider[i].dataIdx === legendEvent.dataItem.dataIdx) {
itemIndex = i;
break;
}
}
//store the object into the dataItem
legendEvent.dataItem.storedObj = dataProvider[itemIndex];
//remove it
dataProvider.splice(itemIndex, 1);
}
legendEvent.chart.validateData(); //redraw the chart
}
//check if legend is enabled and custom generateFromData property
//is set before running
if (!chart.legend || !chart.legend.enabled || !chart.legend.generateFromData) {
return;
}
var categoryField = chart.categoryField;
var colorField = chart.graphs[0].lineColorField || chart.graphs[0].fillColorsField;
var legendData = chart.dataProvider.map(function(data, idx) {
var markerData = {
"title": data[categoryField] + ": " + data[chart.graphs[0].valueField],
"color": data[colorField],
"dataIdx": idx
};
if (!markerData.color) {
markerData.color = chart.graphs[0].lineColor;
}
data.dataIdx = idx;
return markerData;
});
chart.legend.data = legendData;
//make the markers toggleable
chart.legend.switchable = true;
chart.legend.addListener("clickMarker", handleCustomMarkerToggle);
}, ["serial"]);
Demo

How to display the total value of a stacked bar chart in d3js

I've made this Plunkr where I would like to display the total value of each bar, it's currently displaying the total accumulated value of each slice of the bar (the black text) and the total value at the end, I only want to display the total value shown at the end of each bar.
I've commented out my attempt at this here
.text(function(d) {
if (d["x1_"+TEAM] != maxValue){
"display", "none";
} else { return formatText(d["x1_"+TEAM])
}
});
But the max value in maxValue is of course the max value of all bars combined.
Is a similar solution possible or do I have to sort the data in a completely new function?
Any help is appreciated!
There are easier and better ways to create a selection with the total of each stacked bar. However, using your code and applying the minimum possible changes, just show the third object for each stacked bar:
.text(function(d, i) {
if (i === 2) return formatText(d["x1_" + TEAM])
});
Or, if you want to do it without a magic number:
.text(function(d, i) {
if (i === textOnBar4.data().length - 1) return formatText(d["x1_" + TEAM])
});
Here is your updated plunker: https://plnkr.co/edit/iovKsrx0Eg4pTUWTDUaA?p=preview

Coffeescript: Conditional variable in for loop not declared correctly

I am using coffeescript to render a plot graph. I had previously posted a question about setting up the conditionals; which I believe is now solved. However, the variable curr_visibility, used for one of the conditionals, is causing an issue I think, because it is not defined correctly. The graph plot essentially works like this; a 0 (not visible) or 1 (visible) is assigned to each point on the graph (the points are used to draw a line that is essentially a terrain profile coming from a map using a DEM image). I am attaching a screenshot which illustrates my bug (LV = lastVisibilty and CV = curr_visibility). The variable curr_visibility is inside a for loop. I need to make sure that it is updated after each iteration, but I am just not sure it is set up properly to work inside my fillColor: if conditional statement. the code starts with two empty sets- line = [] and datasets = [] Plot graph showing the bug. The area between LV and CV should be red for No visibility
prev_visibility = data[0].visibility
for elem, index in data
curr_visibility = elem.visibility
point = [
index
elem.geometry[2]
]
line.push point
unless prev_visibility is curr_visibility
datasets.push line
line = [point]
prev_visibility = curr_visibility
datasets.push line
line = []
lastVisibility = data[0].visibility
newfillColor = if lastVisibilty == 0 && curr_visibility == 0
"#C90E30"
else if lastVisibilty == 0 && curr_visibility == 1
"#439C32"
else if lastVisibilty == 1 && curr_visibility == 0
"#C90E30"
else
"#439C32"
for set in datasets
line.push
data: set,
lines:
show: true
fill: true
opacity: 0.7
fillColor: newfillColor
lastVisibility = 1 - lastVisibility
OK, with the help of a coworker, I was able to resolve this issue. First, in the code above, every instance of the variable prev_visibility was removed. It was determined not to be necessary. Second, we determined that index method needed to be utilized to relate to a new variable, next_visibility, that would compare the current visibility value of a point to that of the next for every iteration (I hope I'm explaining this correctly). To do this, we added the following:
line.push point
if (index + 1) < data.length
next_visibility = data[index + 1].visibility
else
next_visibility = curr_visibility
unless next_visibility is curr_visibility
datasets.push line
line = [point]
Finally, all of the newFillColor stuff was removed and I reverted back to fillColor: if lastVisibility is 0 then "#C90E30" else "439C32"

Row Chart grouping on two text dimensions [duplicate]

I need to create a rowchart in dc.js with inputs from multiple columns in a csv. So i need to map a column to each row and each columns total number to the row value.
There may be an obvious solution to this but i cant seem to find any examples.
many thanks
S
update:
Here's a quick sketch. Apologies for the standard
Row chart;
column1 ----------------- 64 (total of column 1)
column2 ------- 35 (total of column 2)
column3 ------------ 45 (total of column 3)
Interesting problem! It sounds somewhat similar to a pivot, requested for crossfilter here. A solution comes to mind using "fake groups" and "fake dimensions", however there are a couple of caveats:
it will reflect filters on other dimensions
but, you will not be able to click on the rows in the chart in order to filter anything else (because what records would it select?)
The fake group constructor looks like this:
function regroup(dim, cols) {
var _groupAll = dim.groupAll().reduce(
function(p, v) { // add
cols.forEach(function(c) {
p[c] += v[c];
});
return p;
},
function(p, v) { // remove
cols.forEach(function(c) {
p[c] -= v[c];
});
return p;
},
function() { // init
var p = {};
cols.forEach(function(c) {
p[c] = 0;
});
return p;
});
return {
all: function() {
// or _.pairs, anything to turn the object into an array
return d3.map(_groupAll.value()).entries();
}
};
}
What it is doing is reducing all the requested rows to an object, and then turning the object into the array format dc.js expects group.all to return.
You can pass any arbitrary dimension to this constructor - it doesn't matter what it's indexed on because you can't filter on these rows... but you probably want it to have its own dimension so it's affected by all other dimension filters. Also give this constructor an array of columns you want turned into groups, and use the result as your "group".
E.g.
var dim = ndx.dimension(function(r) { return r.a; });
var sidewaysGroup = regroup(dim, ['a', 'b', 'c', 'd']);
Full example here: https://jsfiddle.net/gordonwoodhull/j4nLt5xf/5/
(Notice how clicking on the rows in the chart results in badness, because, what is it supposed to filter?)
Are you looking for stacked row charts? For example, this chart has each row represent a category and each color represents a sub-category:
Unfortunately, this feature is not yet supported at DC.js. The feature request is at https://github.com/dc-js/dc.js/issues/397. If you are willing to wade into some non-library code, you could check out the examples referenced in that issue log.
Alternatively, you could use a stackable bar chart. This link seems to have a good description of how this works: http://www.solinea.com/blog/coloring-dcjs-stacked-bar-charts

Display vertical line on intersection point

I'm trying to represent a Pareto chart with Highcharts, as you can see here.
The horizontal line shows the 80% value, but now I wanted to display a vertical line where that horizontal 80% line intersects with the "Acumulated" chart series.
This is an example of what I'm trying to achieve:
Is there a way to do it?
Another option would be to get the "x" value of the "Acumulated" spline where it's "y" value is "80", that way I could then draw the line manually.
Is this even possible with the Highcharts API?
I know that it's possible to get the values of a point in a series, but that isn't enough in this case:
var point = chart.get('accumulated').data[2];
I have find it for 80:20 calculation.
First I have find the first value in series from Spline data which greater than or equal to 80.
i.e. >= 80
Suppose it is DataX
Then find out the that index in array plus one for DataX.
i.e. DataX location is DataIndex = index+1
(as array start from 0th calculation need plus one)
formula is
DataX : DataIndex :: 80: ?
let the question mark is xIndexOf80
then xIndexOf80 = (DataIndex *80)/(DataX ).
xIndexOf80 is nothing but position of 80 on X axis.
which gives you exact marks on X-Axis
function findInetrSectionPoint(arrSplineData) {
var intLen = arrSplineData.length;
for (var index = 0; index < intLen; index++) {
if (arrSplineData[index] >= 80) {
interSectPoint = ((index + 1) * 80) / arrSplineData[index] - 1;
break;
}
}
return interSectPoint;
}
Here is the Plunker
You can calculate position of 80% point and then use http://api.highcharts.com/highstock#Renderer rect. Apart from that you can also check this option http://api.highcharts.com/highstock#Axis.addPlotLine() / http://api.highcharts.com/highstock#yAxis.plotLines

Categories

Resources