5

So, I have been playing with high charts and KO for the past week. The syntax for the high charts graph is very simple and easy to read.

I'm coming from Java and I am pretty new to JS, so I'm not sure how to really play with the scope.

Is there anyway to use or combine knockout with high charts to easily make a dynamic graph? Can I simply put them in the same JS file?

The high charts code looks so simple there has to be an easy solution! Thanks in advance for the input/help!

Here is the code for my high charts graph:

$(function() {
    var chart;
    //alert(users);
    $(document).ready(function() {
        chart = new Highcharts.Chart({
            chart : {
                renderTo : 'container',
                type : 'line',
                marginRight : 130,
                marginBottom : 25
            },

            title : {
                text : 'Body Weight',
                x : -20 //center
            },
            xAxis : {
                categories : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
            },

            yAxis : {
                title : {
                    text : 'Weight (lbs)'
                },
                plotLines : [{
                    value : 0,
                    width : 1,
                    color : '#808080'
                }]
            },
            tooltip : {
                formatter : function() {
                    return '<b>' + this.series.name + '</b><br/>' + this.x + ': ' + this.y + 'lbs';
                }
            },
            legend : {
                layout : 'vertical',
                align : 'right',
                verticalAlign : 'top',
                x : -10,
                y : 100,
                borderWidth : 0
            },
            series : [{
                name : 'George',
                data : [185,190,185,180]
            }, {
                name : 'Bindu',
                data : [115,110,112,115]
            }, {
                name : 'Phil',
                data : [195,190,190,185]
            }]
        });
    });
});

And here is my KO model:

/*
 * Reading class
 * containts health readings
 */
function Reading(date,weight,glucose)
{
    var self = this;

    self.date=ko.observable(date);
    self.weight=ko.observable(weight);
    self.glucose=ko.observable(glucose);
    self.readingId=ko.observable(0);

      self.formattedWeight = ko.computed(function(){
        var formatted = self.weight();

        return formatted+" lb"
    });
}

/*
 * User class
 * contains a user name, date, and an array or readings
 */
function User(name,date,readings) {
    var self = this;

    self.name = name;
    self.date = date;
    self.userId = ko.observable(0);
    self.readingsArray = ko.observableArray([
        new Reading(99,22,88),new Reading(11,22,33)
    ]); 
}
// Overall viewmodel for this screen, along with initial state
function userHealthModel() {
    var self = this;
    self.inputWeight = ko.observable();
    self.inputDate = ko.observable();
    self.inputId = ko.observable();
    self.inputGlucose = ko.observable();

    // Operations
    self.subscribe = function(){
        var users = this.users();
        for(var x = 0; x < users.length; x++)
        {
            self.users()[x].userId(x); 
        }

    }
    self.addUser = function(){
        self.users.push(new User("",0,0,(new Reading(0,0))));
        self.subscribe();
    }

   self.removeUser = function(userName){
    self.users.remove(userName);
    self.subscribe();}

     //adds a readings to the edittable array of readings (not yet the reading array in a user)
    self.addReading = function(){
        var iD = self.inputId();
        var date = self.inputDate();
        var weight = self.inputWeight();
        var glucose = self.inputGlucose();
        if((weight&&date)||(glucose&&date))
        {
            self.users()[iD].readingsArray.push(new Reading(date,weight,glucose));
            self.readings.push(new Reading(date,weight,glucose));
        }
        else{
            alert("Please complete the form!")
        }
    }
    self.removeLastReading = function(userName){
        var lastIndex = (userName.readingsArray().length)-1;
        var removeThisReading = userName.readingsArray()[lastIndex];
        userName.readingsArray.remove(removeThisReading);
    }


    //editable data
     self.readings = ko.observableArray([
        new Reading(12,99,3),new Reading(22,33,2),
        new Reading(44,55,3)
    ]);

      self.users = ko.observableArray([
        new User("George",2012),new User("Bindu",2012)
    ]);
    self.subscribe();

}
john_science
  • 6,325
  • 6
  • 43
  • 60
Phil Ninan
  • 1,108
  • 1
  • 14
  • 23

2 Answers2

3

I have experience with both KO and Highcharts but weirdly I've never combined them in this manner. It would be nice to simply hold a KO model in memory that mimics the config object of HC and the two just play nice. Unfortunately this isn't going to happen as KO's observables are functions and HC expects pure json. You could do this but you'd have to destroy the chart each time and recreate with an unmapped copy of your KO model so probably not what you were thinking.

When you say dynamic I am assuming you mean the data only?

There are a number of methods on the chart/series objects documented here that can be used to connect your model to you chart. A really simple and incomplete way would be something like.

self.addUser = function() {
    var user = new User("Foo", 0, 0, new Reading(0, 0));
    self.users.push();
    self.subscribe();

    // chart handler
    /// need to convert the readingsArray into a data array
    chart.addSeries({ name: user.name, data: [180, 175, 195, 180] });
}

Alternatively you could subscribe to the users array and update the chart accordingly. (Note I didn't map your user readings data to the array but you get the idea)

viewModel.users.subscribe(function() {
  .. update chart here
});

See here for more details on subscribing to observables.

Here is the fiddle I created to test this.

http://jsfiddle.net/madcapnmckay/uVTNU/

I'm afraid there is going to be a bit of manual mapping of your model objects to highchart's data format.

Hope this helps.

madcapnmckay
  • 15,782
  • 6
  • 61
  • 78
  • wow thats awesome! i browsed through the HC docs very quickly. wish they have a addPoint method as well! – Phil Ninan Mar 29 '12 at 23:41
  • as for the subscribe thing... so basically whenever the array is updated the viewModel.users.subscribe(function(){}); would run? that seems like a cleaner way to go about this. thanks again capn! second time this week youve answered one of my questions! – Phil Ninan Mar 29 '12 at 23:45
  • Yep I think that's the best approach. BTW there is an add point method http://www.highcharts.com/demo/dynamic-click-to-add you can do series.addPoint([x, y]) – madcapnmckay Mar 29 '12 at 23:57
  • it looks like series is an array in that example. but do you think i would be able to use that method on multiple series? – Phil Ninan Mar 30 '12 at 00:07
1

As with most 3rd-party plugins that manipulate the DOM, they are almost always best approached through the use of a custom binding handler. I don't have experience with Highcharts, but from the looks of it, it seems to take an approach to initialization and updating similar to other libraries that do well as custom KO bindings.

neverfox
  • 6,680
  • 7
  • 31
  • 40