ChartBased.JS

About the framework


ChartBased.JS is framework I made that creates different graphs like the ones below. It gives user the ability to roll out their own customized charts with the added benefit of being able to control chart appearances. Made for designers, developers, and everyone else in between.


Getting Started


To import the library to your html file, copy this:

                    
                        <script src="framework.js"></script >
                    
                

In your html include this line to reference other elements to use

                    
                        
                        'use strict'; 
function my$(idName) { return document.getElementById(idName); }

How to use the framework?


To use the framework create a div in your html

   
                
                    <div id ="canvas"></div>
                 
            

In your javascript tag, instantiate the framework and call on the constructor filling in the data you want.

                
                var graphFw = new GraphFW();
                var barchart = new graphFw.MakeChart({
                    id: "canvas",
                    chartType: "bar"
                });
                
            

Property NameDescription
title The title of the chart.
chartTypeChart type. Input a string either "Bar" or "line". Not case sensitive.
fontSizeFont size of text. Input a number.
widthTotal width of the canvas to draw on. Input a number.
heightTotal height of the canvas to draw on. Input a number.
marginSpacing between canvas and inner graph. Input a number.
xValAn array of data. Can accept either string or number values.
yValAn array of data. Can accept either string or number values.
xAxisTitleTitle for xVal data series.
yAxisTitleTitle for yVal data series.
colorsAn array of colors that the graph can use.
axesTitlesOnA boolean string "true" or "false". Displays the x & y axis titles.
gridLinesOnA boolean string "true" or "false". Displays the x axis grid lines.
xAxisValOnA boolean string "true" or "false". Displays the x axis values.
yAxisValOnA boolean string "true" or "false". Displays the y axis values.
chartTitleOnA boolean string "true" or "false". Displays the chart title
vertTraceOnA boolean string "true" or "false". Displays the vertical bar that follows mouse movement for linechart
toolTipOnA boolean string "true" or "false" for input. Displays values of graph when mouse hovers over it for linechart.
fileNameA file name for the spreadsheet. File extension .csv is automatically appended.
borderBorder style around the canvas. Refer to css style docs.
borderRadiusBorder radius for the canvas, just a number for input.
positionPositioning of the canvas.

Tutorial: Make your own!


Properties to create

The following sections will focus on creating the basis for both the bar and line chart. The list of properties for the graph framework that had to be created is listed in the table above and the code shown below. Error handling is done in code but not shown here for brevity.
Link to canvas api.

            
                // if any of the param properties do not exist, set them to default values.
                params.title = params.title || "Chart Title";
                params.chartType = params.chartType || "Bar";
                params.fontSize = params.fontSize || 12; 
                params.width = params.width || 500;     //width of canvas
                params.height = params.height || 500;   //height of canvas
                params.margin = params.margin || 10;    //margin between div and canvas
                params.xVal = params.xVal || ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"];
                params.yVal = params.yVal || [10, 20, 30, 40, 50];
                params.xAxisTitle = params.xAxisTitle || "X-Axis Title";
                params.yAxisTitle = params.yAxisTitle || "Y-Axis Title";
                params.colors = params.colors || ["lightblue", "black", "lightgreen", "orange", "salmon", "tan", "mediumslateblue"];
                params.axesTitlesOn = params.axesTitlesOn || "true";
                params.gridLinesOn = params.gridLinesOn || "true";
                params.xAxisValOn = params.xAxisValOn || "true";
                params.yAxisValOn = params.yAxisValOn || "true";
                params.chartTitleOn = params.chartTitleOn || "true";
                params.vertTraceOn = params.vertTraceOn || "true";
                params.toolTipOn = params.toolTipOn || "true";
                params.fileName = params.fileName || 'testFile';

                //Make sure both array lengths are the same
                if(params.xVal.length !== params.yVal.length){
                    alert("Array lengths differ! Please recheck array set cardinality.");
                    return;
                }

                /* The scope of these variables is local, cannot be accessed outside this function, 
                 * but because of "closure", they can be accessed by methods within this function.    */
                var title = params.title;
                var chartType = params.chartType;
                var width = params.width;   //full graph width
                var height = params.height; //full graph height
                var margin = params.margin;
                var arrX = params.xVal;
                var arrY = params.yVal;
                var xAxisTitle = params.xAxisTitle;
                var yAxisTitle = params.yAxisTitle;
                var fontSize = params.fontSize;
                var colors = params.colors;
                var axesTitlesOn = params.axesTitlesOn;
                var gridLinesOn = params.gridLinesOn;
                var xAxisValOn = params.xAxisValOn;
                var yAxisValOn = params.yAxisValOn;
                var chartTitleOn = params.chartTitleOn;
                var vertTraceOn = params.vertTraceOn;
                var toolTipOn = params.toolTipOn;
                var fileName = params.fileName;
                var factor = 8;
                var xPos = factor * margin;             //inner graph x position
                var yPos = factor * margin;             //inner graph y position
                var graphWidth = width - 2 * xPos;      //inner graph width w/o labels area
                var graphHeight = height - 2 * yPos;    //inner graph height w/o labels
                var barWidth = graphWidth/arrX.length - margin;
                var barHeight = 0;
            

            

This is the basic skeleton of the html code calling on the framework

                
                    <body>
                    <div id ="canvas"></div>
                    <script src="framework.js"> </script
                    <script>
                        'use strict';
                        //To avoid rewriting document.getElementById()
                        function my$(idName) {
                            return document.getElementById(idName);
                        }

                        var graphFw = new GraphFW();
                        var barchart = new graphFw.MakeChart({
                            id: "canvas",
                            chartType: "bar"
                        });
                    </script>
                    </body>   
                
            

Creating Grid Lines

In order to create gridLines, a gridLines function was created that drew horizontal lines as grids based on the canvas's inner graph height & width. This was calculated by yPos & xPos being the offset of the user selected margin space from where the inner graph starts. From there, the line is drawn from offset to graphWidth for the x axis, looped for each y value.
Link to drawing lines.

                
                    //Draw the horizontal grid lines
                    function drawGridLines() {
                      ctx.strokeStyle = "grey";
                      var ySpacing = graphHeight / arrY.length;
                      for (var i = 0; i < arrY.length; i++) {
                        var yPosMark = yPos + i * ySpacing;
                        ctx.beginPath();
                        ctx.moveTo(xPos, yPosMark);
                        ctx.lineTo(xPos + graphWidth, yPosMark);
                        ctx.stroke();
                      }
                      //draw 0 axis line
                      ctx.beginPath();
                      ctx.moveTo(xPos, yPos + graphHeight);
                      ctx.lineTo(xPos + graphWidth, yPos + graphHeight);
                      ctx.stroke();
                    }
                
            

See the Pen Gridlines by Hai (@casuallycoded) on CodePen.


Making Titles

To create Axes titles, we need two functions, drawAxesTitles() & drawChartTitle(). Drawing the chart title, the position must be calculated as half the graph width and half the offset from y zero-line. Same is done for the x axis title with a different calculation. The important thing to take away from drawAxesTitles() is the y axis title. In order to draw rotated, the canvas state must be saved because when the canvas flips, it loses the original image. Remember the unit circle? To write on the side, that's a 3pi/2 rotation. From there, restore() is called to bring back the original canvas.
Link to writing rotated text.

                
                    //Draw the Axes Titles
                    function drawAxesTitles(){
                        //Draw x axis title label
                        ctx.fillText(xAxisTitle, xPos + graphWidth/2 , yPos + graphHeight + fontSize*3);
                        ctx.save();

                        //Draw y axis title label
                        ctx.translate(xPos - fontSize*3, yPos + graphHeight/2);
                        ctx.rotate(3*Math.PI/2);
                        ctx.fillText(yAxisTitle, 0, 0);
                        //ctx.fillText("Y-Axis Title", xPos - 36, yPos + graphHeight/2);
                        ctx.restore();
                        console.log("Y-Axis is drawn");
                    }
                    //Draw the chart title
                    function drawChartTitle(){
                        console.log("drawChartTitle() is called");
                        ctx.font = "30px Arial";
                        ctx.fillText(title, width/2, yPos/2); 
                    }
                
            

See the Pen Drawing Titles by Hai (@casuallycoded) on CodePen.


Drawing Axis Values

For writing the axis values, the space was calculated by dividing the graphWidth & graphHeight in the canvas by the number of elements in the array. Not shown is a findMax() & findMin() function to calculate largest values in the array. This is done to get the difference and calculate the rate of increase and generate spaced out values accordingly. Basically, calculations are made to print out a suitable grid coordinate system. Note a few lines of code are missing which prevent to some degree the text size from being too big in the codepen.

                
                     //Draw the X axis values
                    function drawXAxesValues(){
                      console.log("X value labels");
                      var xPosMark;
                      var yPosMark;
                      var value;
                      ctx.textAlign = "center";
                      for(var i = 0; i < arrX.length; i++){
                        xPosMark = i * (graphWidth/arrX.length) + margin * (factor + 1) +  barWidth/2;
                        value = arrX[i];
                        yPosMark= graphHeight - barHeight + (margin * factor) + fontSize;
                        console.log("xPosMark: " + xPosMark + " yPos: " + yPosMark);
                        ctx.fillText(value, xPosMark, yPosMark);
                      }
                    }
                    drawXAxesValues();
                    //Draw the Y axis values
                    function drawYAxesValues(){
                      console.log("Y value labels");
                      var ySpacing = graphHeight/arrY.length;
                      for(var i = 0; i < arrY.length; i++){
                        var xPosMark = xPos - fontSize;       
                        var yPosMark= yPos + i * ySpacing;
                        ctx.fillText(maxY- maxY/arrY.length * i, xPosMark, yPosMark);
                      }
                      ctx.fillText(0, xPos-fontSize, yPos + graphHeight);   //write 0 for y val
                    }
                    drawYAxesValues();
                
            

See the Pen Writing Axis Values by Hai (@casuallycoded) on CodePen.


Drawing Bar Graph With Animation

To draw the bar graph, the following function was made. What's happening in the code is that in the loop, each time a new height, x position,& y position is computed to draw a rectangle. The trick to the animation is the call from requestAnimationFrame() which calls the the drawBarGraph() function. How it animates is that the percent/percentageComplete is multiplied with the percent value increasing at each call to drawBarGraph(). So what's happening is that rectangles are getting drawn over the older ones. In order to get the color the colorGraph() function, not shown here but in the codepen below.
Link to figuring out a way to animate.
Link to ideas writing the colored gradient function.

                
                    //Gets the new height relative to the maxY (scaling)
                    var scale = maxY;
                    var percent = 0;
                    var step = 2;
                    var percentageComplete = 100;
                    function drawBarGraph(){
                        for(var i = 0; i < arrY.length; i++){
                            ctx.fillStyle = colorGraph(i);
                            barHeight = arrY[i]/scale * graphHeight * percent/percentageComplete;
                            xPos = i * (barWidth + margin) + margin * (factor + 1);
                            yPos = graphHeight - barHeight + (margin * factor);
                            ctx.fillRect(xPos, yPos, barWidth, barHeight);
                        }
                        //animate the drawing
                        if(percent < 100){
                            percent += step;
                            requestAnimationFrame(drawBarGraph);
                        }
                    }
                
            

See the Pen Drawing a Bar Graph with Animation by Hai (@casuallycoded) on CodePen.


Drawing Line Graph With Animation

In order to draw a line graph, the animation specifically is a little trickier. So drawLineGraph() calcaultes the scaled distances between the different data points of the array on the graph (distance between x1 & x2; y1 & y2). Then in inner for loop it aggregates the data points between the distances into a points array object. In animateLinePlot(), like in the one before, what's happening is this function is being called, calling drawLine() [not shown here] to draw each smaller segment. Note, the higher numPoints is, the more pieces to draw but the longer it takes to animate.

                
                    //This animates the lines plot
                    var delta = 0;
                    var numPoints = 25;
                    var points = [];    //needs to be defined globally to store all intermediate points between segments
                    function animateLinePlot(){
                        if(delta < points.length - 3){
                            requestAnimationFrame(animateLinePlot);
                        }
                        drawLine(points[delta].x, points[delta].y, points[delta+1].x, points[delta+1].y, delta);
                        delta++;
                    }

                    //This draws the line graph
                    function drawLineGraph(){
                        var maxYY = findMax(arrY);
                        var xRatio = graphWidth/arrX.length;
                        var x1 = 0; 
                        var y1 = 0;   
                        var x2 = 0;
                        var y2 = 0;
                        //get points to draw
                        for(var i = 0; i < arrY.length - 1; i++){
                            y1 = graphHeight - arrY[i]/maxYY * graphHeight + (margin * factor);
                            y2 = graphHeight - arrY[i+1]/maxYY * graphHeight + (margin * factor);

                            //For the line axis with titles
                            x1 = xRatio * i + xPos + xRatio/2; //since has string labels, add half to align with center of text
                            x2 = xRatio * (i+1) + xPos + xRatio/2; //since has string labels, add half to align with center of text                    
                            

                            //calculate the "numPoints" points between (x1,y1) & (x2,y2)
                            var xPiece = x2 - x1;
                            var yPiece = y2 - y1;     
                            for(var j = 0; j < numPoints; j++){
                                var xPt = x1 + xPiece * j/numPoints;
                                var yPt = y1 + yPiece * j/numPoints;
                                points.push({x: xPt, y: yPt});
                            }
                        }
                        animateLinePlot(points);
                    }
                
            

See the Pen Drawing a Line Graph with Animation by Hai (@casuallycoded) on CodePen.


Exporting to CSV Feature

Creating an export feature is rather easy. In order to do this, create a button and append it to the object. Then in the exportDataToCsv() function, create a new array object, pushing in the array elements from both passed in data series into the array. Then add commas by calling the array join() function. From there, a anchor tag is created an href as below denotating it as a csv file. Finally, the anchor tag has to be clicked as to make sure it automatically opens.
Link to url encodes.
Link to doing doing the file conversion.

                
                    //Create button for download
                    var button = document.createElement("BUTTON");
                    var btnText = document.createTextNode("Download");
                    button.appendChild(btnText);
                    graphObj.appendChild(button);
                    button.style.position = "absolute";
                    button.style.float = "left";
                    button.style.zIndex = 10;
                    //setup onclick event to export data
                    button.onclick = exportDataToCSV;

                    //This function gets the arrays and converts them to a csv file to download
                    function exportDataToCSV(){
                        var arr = [['x', 'y']];
                        for(var i = 1; i <= arrX.length; i++){
                            arr.push([arrX[i-1], arrY[i-1]]);
                        }
                        var csv = [];
                        for(var i = 0; i < arr.length; i++){
                            csv.push(arr[i].join());        //join() automatically adds commas
                        }
                        console.log(csv);
                        var csvString = csv.join("%0A");        //line feed or new line to separate each X, Y row object
                        var anchor = document.createElement("a");
                        anchor.href = "data:attachment/csv," + csvString;
                        console.log("anchor href: " + anchor.href);
                        anchor.target = '_blank';
                        anchor.download = fileName + '.csv';
                        document.body.appendChild(anchor);
                        anchor.click(); //click on the link to automatically open
                    }
                
            

See the Pen Exporting to CSV by Hai (@casuallycoded) on CodePen.


Adding A Tracing Feature & ToolTips

To implement tracing feature, mouse movement had to be detected. The track cursor function calls on both the trace() and drawToolTip() functions to respectively draw the trace on screen and/or the tooltip whenever the mouse is over the canvas. This is done without intefering with the original canvas by creating a second canvas overlaying it. So, when it clears the canvas to update the position of the tooltip and tracer, it only clears the other canvas.
Link to getting mouse events.

                
                    //Create a second canvas to draw over the original graph (i.e. tooltips, traces, etc)
                    var canvasTool = document.createElement('canvas');
                    var ctxTool = canvasTool.getContext('2d');  
                    canvasTool.height = canvas.height;
                    canvasTool.width = canvas.width;
                    canvasTool.style.zIndex = 10;
                    graphObj.appendChild(canvasTool);       

                    //This function creates a vertical line that tracks user movement along the graph 
                    function trace(x, y, offSetGraph){
                        ctxTool.font = 'bold 20px verdana';
                        ctxTool.strokeStyle = "grey";
                        ctxTool.lineWidth = 0.2;
                        //Draw vertical line to trace 
                        if(x > offSetGraph && x < graphWidth + offSetGraph){
                            //clear the graph
                             ctxTool.clearRect(0, 0, width, height);
                             ctxTool.beginPath();
                             ctxTool.moveTo(x, offSetGraph);
                             ctxTool.lineTo(x, offSetGraph + graphHeight);
                             ctxTool.stroke();
                        }
                    }
                    //This function shows the values x, y values in a popup based on mouse movement
                    //Currently the xVal calculations are correct but the yVal calculations need debugging
                    function drawToolTip(x, y){
                        //Create tool tip
                        var variance = 5;
                        for(var i = 0; i < points.length; i++){
                            if((x >= points[i].x - variance && x <= points[i].x + variance) &(y >= points[i].y - variance && y <= points[i].y + variance)){
                                console.log("points matches: " + x + " " + points[i].x + " "+ y + " " + points[i].y);
                                ctxTool.fillStyle = "grey";
                                ctxTool.fillRect(x, y, 80,50);
                                ctxTool.textAlign = "center";
                                ctxTool.font= "8px Arial";
                                ctxTool.fillStyle = "whitesmoke";
                                var yVal = arrY[0] + Math.floor((i+1) * (findMax(arrY) - findMin(arrY))/(points.length-1));//Math.floor((graphHeight - y + offSetGraph)/delta);
                                yVal = yVal.toFixed(2);
                                var xVal = arrX[Math.floor((i+1)/numPoints)];
                                ctxTool.fillText("x: " +  xVal + "   y: " +  yVal, x + 40, y + 25);
                            }
                        }
                        
                    }
                    //This function tracks the mouse movement on the screen gets 
                    //back the position of the cursor on the screen
                    function trackCursor(e){
                        var offSetGraph = factor * margin;
                        var x = e.pageX - canvas.offsetLeft;
                        var y = e.pageY - canvas.offsetTop;
                        trace(x, y, offSetGraph);
                        drawToolTip(x, y);
                    }
                    //Listen for mouse movement events
                    canvas.addEventListener('mousemove', function(event){
                        trackCursor(event);
                    });
                
            

See the Pen Exporting to CSV by Hai (@casuallycoded) on CodePen.


Conclusion & Summary

In summary to rehash what was done to make this framework:

  1. Create the empty div in html.
  2. Create the properties needed for the graph.
  3. Create the grid lines.
  4. Create the titles.
  5. Draw the axis values relative to the inner graph.
  6. Draw the bar graph with animation.
  7. Draw the line graph with animation.
  8. Export the arrays to a csv file format.
  9. Add tracing & tooltip capabilities to the line graph.

Above all, this was a very fun project. I enjoyed learning the intricacies of designing this graphing framework. If I had more time, I would definitely loved to have finished my zooming feature and added a pie chart in. Also I would refactor the different chart abilities into a factory too.


Below is a link to a test page demonstrating some of the capabilities of the framework.