Logarithmic axis scale in Kendo Chart - kendo-ui

I have a scenario in which I need the a axis scale to be displayed in Logarithmic order. I did a little search and found that this option is not available but the posts are almost a year old. Has this functionality been provided in the latest releases?
Here are the links of Kendo forum that I looked into
http://www.kendoui.com/forums/dataviz/chart/logarithmic-scale.aspx
http://www.kendoui.com/forums/dataviz/chart/does-kendo-support-log-scale.aspx

I know this is an old post, but i found it trying to do something like this. I found a workaround, and it may be it help somebody.
I found a way to implement a logarithmic scale in Kendo UI. Basically, the idea is convert the values from its logarithmic form to a linear form, then bound the data with a KendoUI serie ("scattered line" in my case) and replace the labels from the Y axis using a template.
.YAxis(axis => axis
.Numeric()
.Title("BER (dB)")
.Labels(l => l.Template("#= formatLog2('{0:0}', value) #"))
.Reverse()
.AxisCrossingValue(double.MaxValue)
.Tooltip(tooltip => tooltip
.Visible(true)
.Template("#= formatLog('{0:0.00000000000000}', value.y) #")
and the JavaScript function "formatLog2". (it is a scratch code, just to ilustrate the point):
function formatLog2(format, value) {
if (value < 0) value = value * -1;
value = Math.pow(10, value);
return kendo.format(format, value);
}
and the DataSource transform:
using (IDatabase db = Database.Create())
using (DataTable dt = new DataTable())
{
db.ExecuteQuery(dt, Query);
List<UnavailabilityChartPoint> l = new List<UnavailabilityChartPoint>();
foreach (DataRow r in dt.Rows)
{
l.Add(new UnavailabilityChartPoint(
r.Field<DateTime>("Date"),
Math.Log10(r.Field<double>("UnSignalMonthly"))
));
}
return Json(l);
}
the important parts are the linear to log and the log to linear transforms:
value = Math.pow(10, value);
Math.Log10(r.Field<double>("UnSignalMonthly"))
Hope this helps somebody.

Here is an example of log scale.
http://demos.telerik.com/kendo-ui/bar-charts/logarithmic-axis

No, you can't do that in Kendo chart (as of 2013)

Related

Unable to filter individual stacks using dc.js with multiple X keys

Stacked Bar chart not able to filter on click of any Stack
I need to filter all the charts when clicking on any stack, which is not happening and struggling for a few days.
I've created a fiddle with link
http://jsfiddle.net/praveenNbd/09t5fd7v/13/
I feel am messing up with keys creation as suggested by gordonwoodhull.
function stack_second(group) {
return {
all: function () {
var all = group.all(),
m = {};
// build matrix from multikey/value pairs
all.forEach(function (kv) {
var ks = kv.key;
m[ks] = kv.value;
});
// then produce multivalue key/value pairs
return Object.keys(m).map(function (k) {
return {
key: k,
value: m[k]
};
});
}
};
}
I tried to follow this example https://dc-js.github.io/dc.js/examples/filter-stacks.html
Not able to figure out how below code works:
barChart.on('pretransition', function (chart) {
chart.selectAll('rect.bar')
.classed('stack-deselected', function (d) {
// display stack faded if the chart has filters AND
// the current stack is not one of them
var key = multikey(d.x, d.layer);
//var key = [d.x, d.layer];
return chart.filter() && chart.filters().indexOf(key) === -1;
})
.on('click', function (d) {
chart.filter(multikey(d.x, d.layer));
dc.redrawAll();
});
});
Can someone please point me out in the right direction.
Thanks for stopping by.
You usually don't want to use multiple keys for the X axis unless you have a really, really good reason. It is just going to make things difficult
Here, the filter-stacks example is already using multiple keys, and your data also has multiple keys. If you want to use your data with this example, I would suggest crunching together the two keys, since it looks like you are really using the two together as an ordinal key. We'll see one way to do that below.
You were also trying to combine two different techniques for stacking the bars, stack_second() and your own custom reducer. I don't think your custom reducer will be compatible with filtering by stacks, so I will drop it in this answer.
You'll have to use the multikey() function, and crunch together your two X keys:
dim = ndx.dimension(function (d) {
return multikey(d[0] + ',' + d[1], d[2]);
});
Messy, as this will create keys that look like 0,0xRejected... not so human-readable, but the filter-stacks hack relies on being able to split the key into two parts and this will let it do that.
I didn't see any good reason to use a custom reduction for the row chart, so I just used reduceCount:
var barGrp = barDim.group();
I found a couple of new problems when working on this.
First, your data doesn't have every stack for every X value. So I added a parameter to stack_second() include all the "needed" stacks:
function stack_second(group, needed) {
return {
all: function() {
var all = group.all(),
m = {};
// build matrix from multikey/value pairs
all.forEach(function(kv) {
var ks = splitkey(kv.key);
m[ks[0]] = m[ks[0]] || Object.fromEntries(needed.map(n => [n,0]));
m[ks[0]][ks[1]] = kv.value;
});
// then produce multivalue key/value pairs
return Object.entries(m).map(([key,value]) => ({key,value}));
}
};
}
Probably the example should incorporate this change, although the data it uses doesn't need it.
Second, I found that the ordinal X scale was interfering, because there is no way to disable the selection greying behavior for bar charts with ordinal scales. (Maybe .brushOn(false) is completely ignored? I'm not sure.)
I fixed it in the pretransition handler by explicitly removing the built-in deselected class, so that our custom click handler and stack-deselected class can do their work:
chart.selectAll('rect.bar')
.classed('deselected', false)
All in all, I think this is way too complicated and I would advise not to use multiple keys for the X axis. But, as always, there is a way to make it work.
Here is a working fork of your fiddle.

Using Grouped Bars with DC.js

I'm using the grouped bar PR of dc.js and the corresponding grouped bar chart example as a baseline.
For some reason, I have to use numbers in my data as opposed to strings. (Convert "male" and "female" to 1/0). I'm guessing it has to do with the reduce functions I'm using. This also effects my x-axis labels, of course. I'd rather they show the text variations.
ndx = crossfilter(eData),
groupDim = ndx.dimension(function(d) {return d.service;}),
qtySumGroup = groupDim.group().reduce(
function(p,v) { p[v.component] = (p[v.component] || 0) + v.qty; return p; },
function(p,v) { p[v.component] = (p[v.component] || 0) - v.qty; return p; },
function() { return{}; });
I'm also noticing that it doesn't seem to crossfilter the data. When I click one of the bars in a group, it doesn't filter my other charts on the page. What am I missing?
Here's the first part of the answer. In order to use string components/genders for grouping, you'll need to adjust the way data is selected for "stacking" (actually grouping when this version of dc.js is used).
So, you can grab the component names by first walking the data and grabbing the components:
var components = Object.keys(etsData.reduce(function(p, v) {
p[v.component] = 1;
return p;
}, {}));
This builds an object where the keys are the component names, and then pulls just the keys as an array.
Then we use components to select the categories like so:
grpChart
.group(qtySumGroup, components[0], sel_stack(components[0]));
for(var i=1; i<components.length; ++i)
grpChart.stack(qtySumGroup, components[i], sel_stack(components[i]));
This is just the same as the original
grpChart
.group(qtySumGroup, "1", sel_stack('1'));
for(var i=2; i<6; ++i)
grpChart.stack(qtySumGroup, ''+i, sel_stack(i));
except that it is indexing by string instead of integer.
I realize this is not the important part of your question, but unfortunately filtering by stack segments is not currently supported in dc.js. I'll try to return to that part later today if I have time - it should be possible to hack it in using a dimension with compound keys (or using two dimensions) and a custom click event, but I haven't seen anyone try this yet.
It would no doubt be a helpful feature to add to dc.js, even if just as an external customization.
EDIT: I've added an example of filtering the segments of a stack, which should apply equally well for grouped bars (although I haven't tried it with your code). The technique is explained in the relevant dc.js issue.

editable scatter chart using dc.js

What is a good approach to have a scatter plot in which the data can be edited in the plot itself with a click action?
The idea is to spot outliers in the data in the plot and filter the values in the plot itself, rather than having to change the source data.
Even better would be to remove the data from the crossfilter, but a solution that just filters is acceptable.
I've come up with a solution using the current dc.js (beta 32).
It does not support the brush (needs to have .brushOn(false)) - I'll explain in the enhancement request why this would need some changes to dc.js.
But it does support clicking on points to toggle them, and the reset link. (Clicking on the background to reset is also possible but not implemented here.)
What we'll do is define our own ExcludePointsFilter with the standard dc.js filter signature:
function compare_point(p1, p2) {
return p1[0] === p2[0] && p1[1] === p2[1];
}
function has_point(points, point) {
return points.some(function(p) {
return compare_point(point, p);
});
}
function ExcludePointsFilter(points) {
var points2 = points.slice(0);
points2.filterType = 'ExcludePointsFilter';
points2.isFiltered = function(k) {
return !has_point(points2, k);
};
return points2;
}
We'll calculate a new set of points each time one is clicked, and replace the filter:
scatterPlot.on('pretransition.exclude-dots', function() { #1
// toggle exclusion on click
scatterPlot.selectAll('path.symbol') #2
.style('cursor', 'pointer') // #3
.on('click.exclude-dots', function(d) { // #4
var p = [d.key[0],d.key[1]];
// rebuild the filter #5
var points = scatterPlot.filter() || [];
if(has_point(points, p))
points = points.filter(function(p2) {
return !compare_point(p2, p);
});
else
points.push(p);
// bypass scatterPlot.filter, which will try to change
// it into a RangedTwoDimensionalFilter #6
scatterPlot.__filter(null)
.__filter(ExcludePointsFilter(points));
scatterPlot.redrawGroup();
});
});
Explanation:
Every time the chart is rendered or redrawn, we'll annotate it before any transitions start
Select all the dots, which are path elements with the symbol class
Set an appropriate cursor (pointer-hand may not be ideal but there aren't too many to choose from)
Set up a click handler for each point - use the exclude-dots event namespace to make sure we're not interfering with anyone else.
Get the current filter or start a new one. Look to see if the current point being clicked on (passed as d) is in that array, and either add it or remove it depending.
Replace the current filters for the scatterplot. Since the scatter plot is deeply wed to the RangedTwoDimensionalFilter, we need to bypass its filter override (and also the coordinateGridMixin override!) and go all the way to baseMixin.filter(). Yes this is weird.
For good measure, we'll also replace the filter printer, which normally doesn't know how to deal with an array of points:
scatterPlot.filterPrinter(function(filters) {
// filters will contain 1 or 0 elements (top map/join is just for safety)
return filters.map(function(filter) {
// filter is itself an array of points
return filter.map(function(p) {
return '[' + p.map(dc.utils.printSingleValue).join(',') + ']';
}).join(',');
}).join(',');
});
Here is a working example in a fiddle: http://jsfiddle.net/gordonwoodhull/3y72o0g8/16/
Note, if you then want to do something with the excluded points, you can read them from scatterPlot.filter() - the filter is the array of points with some annotation. You may even be able to reverse the filter and then call crossfilter.remove() but I'll leave that as an exercise.

d3 ordinal scale that adjusts to cluster size

How can I shift the start point of each nested group according to how much space that group requires in each cluster to mainatin a constant bar width? The ordinal scale allocates the same space for each cluster and then decreases the bar width to accomodate more bars but I want to adjust in a linear fashion to maintain the bar size. Any ideas?
Thanks
Have you tried swapping the 'ordinal' with 'linear'.. because you can specify multiple domain and range values just like in ordinal scales and d3 will use all of them like an ordinal scale, but keeps the smooth linear interpolation.. Check out how this behaves.
var scale = d3.scale.linear().domain([1,2,3,4]).range([1,20,300,4000])
Ok so after gaining more insight, it looks like you do have to copy the original scale and alter it to make a new scale for each object that has a different... umm, scale. Funny that obvious clue never entered my mind till just now.
I copied that d3 example into jsfiddle and altered it a little and seems to work. Was this the effect you were going for?
http://jsfiddle.net/2ktVC/2/
I mostly just replaced the harmless forEach loop in the example with these here guns.
data.forEach(function(d,i){/*-=======================*/
var myNames = d3.keys(d).filter(function(key) { ////
if(key !== "State" && d[key]){ /*-==============-*/
allAgeNames[key]
= true;
return true;
} return false;
//==========
xScales[i] = d3.scale.ordinal();/*==========*/
d.ages = myNames.reduce(function(arr, name) {
if(+d[name]) arr.push({
value:+d[name],
x:xScales[i],
name: name
});/*===*/
return arr;
},[]);/*
//||||||||||||||||||||||||||==========================----*/
xScales[i].domain(myNames).rangeRoundBands([0, x.rangeBand()], 0);
d.x = /*|_|_|_|_|_*/
xScales[i];
d.ages.x =
xScales[i]
});
Im kidding, stack overflow wont let me link to jsfiddle without some reference code.... so out come the jokes.

Trouble with filters and triggers in dc.js - re-drawing is out of sync

I have two plots: a line plot and a bubble plot. When I click on the bubble plot, I want the line plot to be updated so that it is drawn with only the data related to that 'bubble'. This is different from the standard implementation whereby clicking would add or remove the data from the existing filter.
If you look at the image you can see that although 'model 0' is selected the plotted hazard (y-scale in plot 1) does not correspond.
And now when I click on 'model 5', I get the opposite.
My current implementation is posted as a jsfiddle here. I can see from the attached data table that I am achieving what I want, but the line plot does not re-draw correctly. In fact, it seems to re-draw with the last filter, not the new one.
This implementation is hacked from here: in particular, the renderlet and on("filtered", function (chart) { lines. However, to make this work, I have had to comment out the plot1.filter(chart.filter()); line for the second plot.
I don't really understand why a renderlet and the on("filtered" ... or on("postRedraw" ... listeners are needed together.
I have been round the houses on this one, so any suggestions would be very gratefully received.
I tried to simplify the jsfiddle to isolate the problem. Here is the adapted jsfiddle: http://jsfiddle.net/djmartin_umich/mKz7A/
Your plot2 keyAccessor accessed the df value from the p.value.df rather than using a dimension on df. My guess is that this is what was causing problems. Here is the adapted code:
dfDim = ndx.dimension(function (d) {return d.df;});
...
plot2.width(300)
.height(250)
.dimension(dfDim)
I also noticed that your plot2 valueAccessor and radiusAccessor were not using a computed average. Your code would overwrite est and estse for each record added or removed from the group. Here is the adapted code that computes the average:
dfGroup = dfDim.group().reduce(
//add
function (p, v) {
++p.count;
p.est += v.est;
p.avg_est = p.est / p.count;
p.estse += v.estse;
p.avg_estse = p.estse / p.count;
return p;
},
//remove
function (p, v) {
--p.count;
p.est -= v.est;
p.avg_est = p.est / p.count;
p.estse -= v.estse;
p.avg_estse = p.estse / p.count;
return p;
},
//init
function (p, v) {
return {
count: 0,
est: 0,
estse: 0,
avg_est: 0,
avg_estse: 0
};
});
After these changes, I believe the code behaves as you wanted.

Resources