change color of single dataLabel in ApexCharts - apexcharts

I want to change dataLabel color for specific value in my bar chart.
documentation says:
Also, if you are rendering a bar/pie/donut/radialBar chart, you can pass a function which returns color based on the value.
I know this is for bar colors but I tried to use it in dataLabel colors. of course it didn't work. any idea how to do it?
my codepen: https://codepen.io/osmanyasircankaya/pen/gOXELmB
style: {
colors: [
function ({ w }) {
if (w.config.series[0].data[4] > 3) {
return "#ff0014";
} else {
return "#1f52b0";
}
},
],
},
docs:
https://apexcharts.com/docs/options/colors/
https://apexcharts.com/docs/options/datalabels/

In your function you checking value of single dataPoint over and over data[4]. What you need to do is checking current series and dataPoint like this:
function ({ seriesIndex,dataPointIndex, w }) {
if (w.config.series[seriesIndex].data[dataPointIndex] > 3) {
return "#ff0014";
} else {
return "#1f52b0";
}
},

Related

How to highlight a column programmatically in AMCharts 4?

In AMCharts version 3, there is a demo showing how to highlight a particular column.
Is this possible using AMCharts version 4? For example, in the Simple Column demo, highlight the UK column based on its value (ie, where country = 'UK').
I tried modifying the example at https://stackoverflow.com/a/54358490/906814 but I can't get a handle on the columns in order to assess their values and then apply the active state highlight (JSFiddle).
// copied from https://stackoverflow.com/a/54358490/906814 but not working yet
var activeState = series.columns.template.states.create("active");
activeState.properties.fill = am4core.color("#E94F37");
series.columns.each(function(column) {
alert("column") // no alert is seen
column.setState("active");
column.isActive = true;
})
There are two approaches you can take.
1) Use an adapter on the column's fill and stroke and check the column value before modifying the color, e.g.
series.columns.template.adapter.add('fill', function(fill, target) {
if (target.dataItem && target.dataItem.categoryX == "UK") {
return "#ff0000";
}
return fill;
});
series.columns.template.adapter.add('stroke', function(stroke, target) {
if (target.dataItem && target.dataItem.categoryX == "UK") {
return "#ff0000";
}
return stroke;
})
Demo
2) Use a property field and set the stroke and fill from your data:
chart.data = [
// ...
{
"country": "UK",
"value": 1122,
"color": "#ff0000"
},
// ...
];
// ...
series.columns.template.propertyFields.fill = "color";
series.columns.template.propertyFields.stroke = "color";
Demo

How do I hide values past the x-axis in chartjs 2.0?

How do I hide values past the x-axis in chartjs 2.0? You will notice the chart juts past the -60 mark. The x-axis uses a time scale and I have the max and min values set.
Here's my chart configuration:
{
"type":"line",
"data":{
"datasets":[
{
"label":"Scatter Dataset",
"data":[
{
"x":"2016-09-16T16:36:53Z",
"y":88.46153846153845
},
...
{
"x":"2016-09-16T16:37:54Z",
"y":88.3076923076923
}
],
"pointRadius":0,
"backgroundColor":"rgba(0,0,255,0.5)",
"borderColor":"rgba(0,0,255,0.7)"
}
]
},
"options":{
"title":{
"display":true,
"text":"Water Level Over Last 60 Seconds"
},
"animation":false,
"scales":{
"xAxes":[
{
"type":"time",
"position":"bottom",
"display":true,
"time":{
"max":"2016-09-16T16:37:54Z",
"min":"2016-09-16T16:36:54.000Z",
"unit":"second",
"unitStepSize":5
},
"ticks":{
callback: function(value, index, values) {
return "-" + (60 - 5 * index);
}
}
}
],
"yAxes":[
{
"display":true,
"ticks":{
}
}
]
},
"legend":{
"display":false
}
}
}
You can achieve this using Chart.js plugins. They let you handle events occuring while creating, updating or drawing the chart.
Here, you'll need to affect before the chart is initialised :
// We first create the plugin
var cleanOutPlugin = {
// We affect the `beforeInit` event
beforeInit: function(chart) {
// Replace `ticks.min` by `time.min` if it is a time-type chart
var min = chart.config.options.scales.xAxes[0].ticks.min;
// Same here with `ticks.max`
var max = chart.config.options.scales.xAxes[0].ticks.max;
var ticks = chart.config.data.labels;
var idxMin = ticks.indexOf(min);
var idxMax = ticks.indexOf(max);
// If one of the indexes doesn't exist, it is going to bug
// So we better stop the program until it goes further
if (idxMin == -1 || idxMax == -1)
return;
var data = chart.config.data.datasets[0].data;
// We remove the data and the labels that shouldn't be on the graph
data.splice(idxMax + 1, ticks.length - idxMax);
data.splice(0, idxMin);
ticks.splice(idxMax + 1, ticks.length - idxMax);
ticks.splice(0, idxMin);
}
};
// We now register the plugin to the chart's plugin service to activate it
Chart.pluginService.register(cleanOutPlugin);
The plugin is basically a loop through the data to remove the values that shouldn't be displayed.
You can see this plugin working in a live example on jsFiddle.
For instance, the following chat with a min set to 2 and a max to 6 ...
... would give the following result :

Chart.js bar color based on labels values

The code I need is here:
chart.js bar chart color change based on value
Dola changes the color of the bars based on the values of the datasets using myObjBar.datasets[0].bars
I want to do the same thing but with the labels values (good, average, bad) e.g.
var barChartData = {
labels: ["good", "average", "bad"],
datasets: [
{
data: [1, 3, 10]
}
]
};
var ctx = document.getElementById("mycanvas").getContext("2d");
window.myObjBar = new Chart(ctx).Bar(barChartData, {
responsive : true
});
var bars = myObjBar.labels[0]; //I need this line
for(i=0;i<bars.length;i++){
var color="green";
if(bars[i].value=="bad"){
color="red";
}
else if(bars[i].value=="average"){
color="orange"
}
else{
color="green"
}
bars[i].fillColor = color;
}
myObjBar.update();
Instead of using bars[i].value property, you can use bars[i].label which gives you the label of the xAxe.
So in your loop, change to this :
for(i=0;i<bars.length;i++){
var color="green";
if(bars[i].label == "bad"){
color="red";
}
else if(bars[i].label == "average"){
color="orange"
}
else{
color="green"
}
bars[i].fillColor = color;
}
You can find the full code in this jsFiddle and here is its result :

"Total rows" in custom Power BI visualizations

I have a question about creating the custom visualization in Power BI.
I want to implement a "total row" functionality which is available in the built-in matrix visualization. The main concept is to automatically sum-up every value and group it by the rows. This is how it's looks like on the matrix visualization:
But, to be honest, I don't know how to achieve this. I try different things but I can't receive this grouped values in the dataViews.
I tried to analyze the built-in matrix.ts code but it's quite different that the custom visualizations code. I found the customizeQuery method which set the subtotalType property to the rows and columns - I tried to add this in my code but I don't see any difference in the dataViews (I don't found the grouped value).
Currently my capabilities.dataViewMappings is set like this:
dataViewMappings: [
{
conditions: [
{ 'Rows': { max: 3 } }
],
matrix: {
rows: {
for: { in: 'Rows' },
},
values: {
for: { in: 'Values' }
},
},
}
]
Does anyone know how we could achieve this "total row" functionality?
UPDATE 1
I already found the solution: when we implement the customizeQuery method (in the same way as the customizeQuery method in the matrix.ts code), and then add the reference to it in the powerbi.visuals.plugins.[visualisationName+visualisationAddDateEpoch].customizeQuery then it works as expected (I receive in the dataViews[0].matrix.row.root children elements that has the total values from row).
The only problem now is that I don't know exactly how to add correctly this reference to the customizeQuery method. For example the [visualisationName+visualisationAddDateEpoch] is Custom1451458639997, and I don't know what those number will be (I know only the name). I created the code in my visualisation constructor as below (and it's working):
constructor() {
var targetCustomizeQuery = this.constructor.customizeQuery;
var name = this.constructor.name;
for(pluginName in powerbi.visuals.plugins) {
var patt = new RegExp(name + "[0-9]{13}");
if(patt.test(pluginName)) {
powerbi.visuals.plugins[pluginName].customizeQuery = targetCustomizeQuery;
break;
}
}
}
But in my opinion this code is very dirty and inelegant. I want to improve it - what is the correct way to tell the Power BI that we implement the custom customizeQuery method and it should use it?
UPDATE 2
Code from update 1 works only with the Power BI in the web browser (web based). On the Power BI Desktop the customizeQuery method isn't invoked. What is the correct way to tell the Power BI to use our custom customizeQuery method? In the code from PowerBI-visuals repository using PowerBIVisualPlayground we could declare it in the plugin.ts file (in the same way like the matrix visual is done):
export let matrix: IVisualPlugin = {
name: 'matrix',
watermarkKey: 'matrix',
capabilities: capabilities.matrix,
create: () => new Matrix(),
customizeQuery: Matrix.customizeQuery,
getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => Matrix.getSortableRoles(),
};
But, in my opinion, from the Power BI Dev Tools we don't have access to add additional things to this part of code. Any ideas?
It seems you're missing the columns mapping in your capabilities. Take a look at the matrix capabilities (also copied for reference below) and as a first step adopt that structure initially. The matrix calculates the intersection of rows and columns so without the columns in capabilities its doubtful you'll get what you want.
Secondly, in the matrix dataview passed to Update you'll get a 'DataViewMatrixNode' with isSubtotal: true Take a look at the unit tests for matrix to see the structure.
dataViewMappings: [{
conditions: [
{ 'Rows': { max: 0 }, 'Columns': { max: 0 }, 'Values': { min: 1 } },
{ 'Rows': { min: 1 }, 'Columns': { min: 0 }, 'Values': { min: 0 } },
{ 'Rows': { min: 0 }, 'Columns': { min: 1 }, 'Values': { min: 0 } }
],
matrix: {
rows: {
for: { in: 'Rows' },
/* Explicitly override the server data reduction to make it appropriate for matrix. */
dataReductionAlgorithm: { window: { count: 500 } }
},
columns: {
for: { in: 'Columns' },
/* Explicitly override the server data reduction to make it appropriate for matrix. */
dataReductionAlgorithm: { top: { count: 100 } }
},
values: {
for: { in: 'Values' }
}
}
}],

Couchbase custom reduce function

I have some documents in my Couchbase with the following template:
{
"id": 102750,
"status": 5,
"updatedAt": "2014-09-10T10:50:39.297Z",
"points1": 1,
"points2": -3,
"user1": {
"id": 26522,
...
},
"user2": {
"id": 38383,
...
},
....
}
What I want to do is to group the documents on the user and sum the points for each user and then show the top 100 users in the last week. I have been circling around but I haven't come with any solution.
I have started with the following map function:
function (doc, meta) {
if (doc.user1 && doc.user2) {
emit(doc.user1.id, doc.points1);
emit(doc.user2.id, doc.points2);
}
}
and then tried the sum to reduce the results but clearly I was wrong because I wasn't able to sort on the points and I couldn't also include the date parameter
you need to see my exemple I was able to group by date and show the values with reduce. but calculate the sum I did it in my program.
see the response How can I groupBy and change content of the value in couchbase?
I have solved this issue by the help of a server side script.
What I have done is I changed my map function to be like this:
function (doc, meta) {
if (doc.user1 && doc.user2) {
emit(dateToArray(doc.createdAt), { 'userId': doc.user1.id, 'points': doc.points1});
emit(dateToArray(doc.createdAt), { 'userId': doc.user2.id, 'points': doc.points2});
}
}
And in the script I query the view with the desired parameters and then I group and sort them then send the top 100 users.
I am using Node JS so my script is like this: (the results are what I read from couchbase view)
function filterResults(results) {
debug('filtering ' + results.length + ' entries..');
// get the values
var values = _.pluck(results, 'value');
var groupedUsers = {};
// grouping users and sum their points in the games
// groupedUsers will be like the follwoing:
// {
// '443322': 33,
// '667788': 55,
// ...
// }
for (var val in values) {
var userId = values[val].userId;
var points = values[val].points;
if (_.has(groupedUsers, userId)) {
groupedUsers[userId] += points;
}
else
groupedUsers[userId] = points;
}
// changing the groupedUsers to array form so it can be sorted by points:
// [['443322', 33], ['667788', 55], ...]
var topUsers = _.pairs(groupedUsers);
// sort descending
topUsers.sort(function(a, b) {
return b[1] - a[1];
});
debug('Number of users: ' + topUsers.length + '. Returning top 100 users');
return _.first(topUsers, 100);
}