Kendo UI is a comprehensive HTML5/JavaScript framework for modern web and mobile app development. Kendo UI emerges out to be one of the most promising app developing framework. Though it’s a commercial product, it is backed by an excellent and fast support team. With Kendo UI, we can easily create products without worrying about cross-browser compatibility, standards compliance or touch-device support.
Though Kendo UI provide us a lot of facilities but while developing a product we may sometimes need some features which are not supported by the framework. In this blog we will be discussing about one of such feature – “Draggable Chart”. Well in the github project (available for download) we have implemented this draggable feature for “line”, “bar” and “column” charts but we will be explaining only “line” charts in this blog.
Since this feature is not supported by Kendo so you may get some issues, like:
New chart data not updated – After drag ends, the chart data of that point will not be updated along with the newly changed position. So each time the chart refreshes it will show the old plotted points (original values) and not the changed dragged point.
Tooltip and overlay problem – After changing (dragging) a point if you hover a series or that point then it will show the old value in the tooltip and the tooltip position will also be of the original value.
Since we did not give much effort on this feature we were unable to fix these issues. But if you find this blog helpful and want to contribute then please fork the github project and help us complete this feature so that Kendo framework can include this feature in future.
index.html
<html> <head> <title>Kendo Draggable Chart</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="https://www.innofied.com/wp-content/uploads/2014/04/kendo.common.min.css"> </head> <body> <div class="kendo-chart"></div> <script src="https://www.innofied.com/wp-content/uploads/2014/04/jquery-1.9.1.min.js" type="text/javascript"></script> <script src="https://www.innofied.com/wp-content/uploads/2014/04/kendo.all.min.js" type="text/javascript"></script> <script src="draggablechart.js" type="text/javascript"></script> </body> </html>
For making the chart points draggable we will make use of kendo chart events: “drag” and “dragStart”. For “line” charts, we will make the line intersecting points (circles) draggable. But before starting we need to evaluate the chart boundaries beyond which we will restrict the user to drag the points. Since the chart dimensions are fluid so we preferred to calculate the boundaries every time in the “dragStart” event. Also to retrieve the individual circles and line paths we will set some attributes to them.
dragStart: function(e) { // Calculate the dimensions only if the target is “circle” if(e.originalEvent.target[0].tagName !== 'circle'){ return; } var me = this, circle = $('.kendo-chart').find('svg g g circle'), // Get all the line intersecting points path = $('.kendo-chart').find('svg g g path'), // Get all the line paths pathLength = path.length, // Get the total paths length circleLength = circle.length; // Get the total circles length me.index = 0; // Calculate the chart boundaries if(!self.chartLimits){ self.chartLimits = getChartLimits(); } // Set attribute to the line path for retrieving the individual path path.each(function() { if (!$(this).attr('data-index')) { $(this).attr('data-index', ++me.index); } }); // Set attribute to the circles(points) for retrieving the individual circle for (var k = 0; k < circleLength; k++) { $(circle[k]).attr({ 'data-index': (k % pathLength) + 1, 'data-bar': Math.ceil((k + 1) / pathLength) }); } } // Get the chart area (plot area) limits getChartLimits = function(){ var plotArea = $($('svg path')[2]).attr('d').substr(1), plotAreaDim = plotArea.split(" "), chartLimits = { upperHeightLimit: plotAreaDim[5], // For line/column graphs lowerHeightLimit: plotAreaDim[1], // For line/column graphs upperWidthLimit: plotAreaDim[2], // For bar graphs lowerWidthLimit: plotAreaDim[0] // For bar graphs }; return chartLimits; };
Now we have got all our requirements thus we can start with our main draggable functionality.
drag: function(e){ // Drag points(circle) only if(e.originalEvent.target[0].tagName === 'circle'){ movePathWithPoint(e.originalEvent.target[0], e.originalEvent.event.offsetY); } } movePathWithPoint = function (element, pos) { var pointIndex, path, p_array, pathElement ,circle, pathIndex, chartLimits = self.chartLimits; // Get the target circle element circle= $('circle[data-model-id=' + $(element).attr('data-model-id') + ']'); pointIndex = circle.attr('data-bar') - 1; pathIndex = circle.attr('data-index'); // Get the line path in which the circle lies pathElement = $('svg g g path[data-index=' + pathIndex + ']'); // Restrict dragging outside the chart limits if (pos > chartLimits.lowerHeightLimit && pos < chartLimits.upperHeightLimit) { // In the pathElement the “d” attribute contains all the coordinates of the points and lines path = pathElement.attr('d').substr(1); // Set the line path along with the dragging circle p_array = path.split(" "); p_array[(pointIndex * 2) + 1] = pos; // Change the path coordinate with the new position of the dragged circle path = "M" + p_array.join(" "); pathElement.attr('d', path); // Reset the “d” attribute with the changed coordinates circle.attr('cy', pos); // Reset the circles y-coordinate } }
Summarizing all the code:
draggablechart.js
$(document).ready(function(){ var self = this; $('.kendo-chart').kendoChart({ title: { text: "Revenue By Country" }, legend: { position: "bottom" }, series: [{ name: "United States", data: [15.7, 16.7, 20, 23.5, 26.6] }, { name: "India", data: [67.96, 68.93, 75, 74, 78] }], seriesDefaults: { type: "line" }, valueAxis: { labels: { format: "{0}" }, line: { visible: true } }, categoryAxis: { categories: ['2009', '2010', '2011', '2012'], majorGridLines: { visible: false } }, drag: function(e){ // Drag points(circle) only if(e.originalEvent.target[0].tagName === 'circle'){ movePathWithPoint(e.originalEvent.target[0], e.originalEvent.event.offsetY); } }, dragStart: function(e) { // Calculate the dimensions only if the target is “circle” if(e.originalEvent.target[0].tagName !== 'circle'){ return; } var me = this, circle = $('.kendo-chart').find('svg g g circle'), // Get all the line intersecting points path = $('.kendo-chart').find('svg g g path'), // Get all the line paths pathLength = path.length, // Get the total paths length circleLength = circle.length; // Get the total circles length me.index = 0; // Calculate the chart boundaries if(!self.chartLimits){ self.chartLimits = getChartLimits(); } // Set attribute to the line path for retrieving the individual path path.each(function() { if (!$(this).attr('data-index')) { $(this).attr('data-index', ++me.index); } }); // Set attribute to the circles(points) for retrieving the individual circle for (var k = 0; k < circleLength; k++) { $(circle[k]).attr({ 'data-index': (k % pathLength) + 1, 'data-bar': Math.ceil((k + 1) / pathLength) }); } } }); // Get the chart area (plot area) limits var getChartLimits = function(){ var plotArea = $($('svg path')[2]).attr('d').substr(1), plotAreaDim = plotArea.split(" "), chartLimits = { upperHeightLimit: plotAreaDim[5], // For line/column graphs lowerHeightLimit: plotAreaDim[1], // For line/column graphs upperWidthLimit: plotAreaDim[2], // For bar graphs lowerWidthLimit: plotAreaDim[0] // For bar graphs }; return chartLimits; }; // Draggable functionality var movePathWithPoint = function (element, pos) { var pointIndex, path, p_array, pathElement ,circle, pathIndex, chartLimits = self.chartLimits; // Get the target circle element circle= $('circle[data-model-id=' + $(element).attr('data-model-id') + ']'); pointIndex = circle.attr('data-bar') - 1; pathIndex = circle.attr('data-index'); // Get the line path in which the circle lies pathElement = $('svg g g path[data-index=' + pathIndex + ']'); // Restrict dragging outside the chart limits if (pos > chartLimits.lowerHeightLimit && pos < chartLimits.upperHeightLimit) { // In the pathElement the “d” attribute contains all the coordinates of the points and lines path = pathElement.attr('d').substr(1); // Set the line path along with the dragging circle p_array = path.split(" "); p_array[(pointIndex * 2) + 1] = pos; // Change the path coordinate with the new position of the dragged circle path = "M" + p_array.join(" "); pathElement.attr('d', path); // Reset the “d” attribute with the changed coordinates circle.attr('cy', pos); // Reset the circles y-coordinate } } });
This is it. Hope this blog helps you to some extent and you can use this functionality in your kendo apps. If you get any issue implementing this, do not hesitate to add a comment here.