Reputation: 11
I'm using a single kendoChart to display up to 10 lines of data.
Each line represents process data that may have widely different context and min/max ranges, but all lines are related in time, the categoryAxis. When displayed, each valueAxis correctly shows the scale for the corresponding line.
However, with 10 lines, the 10 valueAxes take up far too much of the screen to be usable for my requirements.
I tried hiding all axes except one with the expectation that the chart would expand to fill up the space taken by the hidden axes, but that does not happen. I get a lone axis surrounded by blank space and the chart's plot area remains the same size.
I tried setting all of the series to use the same valueAxis and then varying the valueAxis min/max per the active channel as chosen by clicking a legend item. This expands the plot area as needed, but removes the ability to see all lines since the scale is specific to one line.
Is it possible for kendoChart to show multiple plots independently from a single valueAxis (e.g. a line with values between 0.5 and 0.7 would appear scaled to the full chart area, and so would a line with values between 25 and 100, but the valueAxis might display either scale.)
Upvotes: 0
Views: 668
Reputation: 11
The solution I used for this problem is more code than I expected to need. Perhaps Telerik's other products have an API for this.
Essentially, I maintain a structure outside of the kendoChart that stores the real data for each series, and this real data is mapped to the expected scale of the currently visible valueAxis. The mapping function is the standard transform from one scale into another.
The valueAxis is 'swapped' depending on which legend item is clicked, and that event triggers a redraw on the chart where all the series data is mapped to the 'active' axis.
Some code snippets. A series is also described as a channel.
// The data structure.
this._channelDescriptors.push({
fullName: ch.fullName || "",
axisTitle: (ch.fullName + axisEUString) || "",
axisFont: ch.axisFont || "",
axisColor: ch.color || "#000000",
realData: [],
minData: Number.MAX_VALUE,
maxData: Number.MIN_VALUE
});
// This event causes the switching of valueAxis for all members of the series.
$("#" + chartID).kendoChart({
// Other kendoChart configurations
//
legendItemClick: function (e) {
var idx = e.seriesIndex;
sncTrender.updateAxis(idx);
e.preventDefault();
},
tooltip: {
visible: true,
template: "#=series.name# : #=kendo.format('{0:N4}', dataItem.realValue)#<br />#=kendo.format('{0:MM-dd HH:mm:ss.fff}', dataItem.Time)#",
},
//
// Other kendoChart configurations
});
// All code snippets are members of a wrapper object.
updateAxis: function (ch) {
if (this.series[ch].visible) {
this.setAxis(ch);
}
},
// Every series is set to the same valueAxis via the selected series' valueAxis.name property.
setAxis: function (ch) {
var i,
channel = this._channelDescriptors[ch];
this._currentChannel = ch;
for (i = 0; i < this.series.length; i++) {
this.series[i].axis = this._channelDescriptors[ch].fullName;
}
// Set the active valueAxis properties. This is the only axis visible maintained for the chart.
this.valueAxis.name = channel.fullName;
this.valueAxis.title.text = channel.axisTitle;
this.valueAxis.title.font = channel.axisFont;
this.valueAxis.line.color = channel.axisColor;
},
// The mapping occurs here, and the transform calculation is this line
// Y: (yRange * (chDesc.realData[k].realValue - newMin) / newRange) + this.valueAxis.min,
//
updateChart: function (allTrends) {
// ...
timeStamps = trendDataResponse.curve.Timestamp;
t1 = trendArgs.t1;
t2 = trendArgs.t2;
xValues = trendDataResponse.curve.X;
yValues = trendDataResponse.curve.Y;
pointCount = xValues.length;
min = Number.MAX_VALUE;
max = Number.MIN_VALUE;
categoryTimes = [pointCount];
newData = [];
for (l = 0; l < pointCount; l++) {
min = Math.min(min, yValues[l]);
max = Math.max(max, yValues[l]);
ts = new Date(timeStamps[l]);
categoryTimes[l] = ts;
// The Y data will be plotted on the chart, but the cursor tooltip will
// use the realValue data. In this way, the series can be visible regardless of
// the valueAxis scaling, but the actual data is also available. Refer to the
// tooltip template.
newData.push({ X: xValues[l], Y: yValues[l], realValue: yValues[l], Time: ts });
}
// Real data for each channel is stored in channelDescriptors.
chDesc = this._channelDescriptors[channelID];
chDesc.realData = newData;
chDesc.minData = min;
chDesc.maxData = max;
// The valueAxis min/max is set only for the 'active' series.
if (this._currentChannel === channelID) {
this.categoryAxis.categories = categoryTimes;
yRange = max - min;
scaleAdjustment = yRange * SNC.CONST_yAxisScaleAdjustmentFactor;
this.valueAxis.min = min - scaleAdjustment;
this.valueAxis.max = max + scaleAdjustment;
}
}
// Scale curves to current axis.
// Use real data for the current series.
for (j = 0; j < this.series.length; ++j) {
chDesc = this._channelDescriptors[j];
if (j === this._currentChannel) {
this.series[j].data = chDesc.realData;
continue;
}
// Use mapped data for all other series.
recalcData = [];
newMin = chDesc.minData;
newMax = chDesc.maxData;
newRange = newMax - newMin;
rangeAdjustment = newRange * SNC.CONST_yAxisScaleAdjustmentFactor;
newMin = newMin - rangeAdjustment;
newMax = newMax + rangeAdjustment;
for (k = 0; k < chDesc.realData.length; ++k) {
recalcData.push({
X: chDesc.realData[k].X,
Y: (yRange * (chDesc.realData[k].realValue - newMin) / newRange) + this.valueAxis.min,
realValue: chDesc.realData[k].realValue,
Time: chDesc.realData[k].Time,
});
}
this.series[j].data = recalcData;
}
chart.redraw();
}
Upvotes: 0