0

I'm attempting to make a heat map binding for a chunk of numbers. So far, numbers that should be part of the associated map should be designated by context

It works up to a point.. I still have to work out the rgb algorithm a bit, but the part that is problematic at is the for loop in it within the setHeat computed function.

What I want is that whenever 'max' or 'min' is changed, i want all elements currently in the 'elements' array to update their color.

But when an element is added to the 'elements' array I just want to set the color of that element, but that requires max and min and so i dont know how to separate the calls properly so that min and max updates will call one and an element update will call the other.

Also i'm open to critiques on how to improve my binding.

<!-- ko foreach:items -->
            <tr>
                <td class="postCell">
                    <div class="ui-post-icon" data-bind="class: $parents[2].setPostIcon(post)"></div>
                    <span data-bind="text:post"></span>
                </td>
                <!-- ko foreach:combinations -->
                <td data-bind="heat:{value:amount,context:'exotics'}"></td>
                <!-- /ko -->
            </tr>
            <!-- /ko -->


ko.bindingHandlers.heat = {
        init: function (element, value, allBindings, viewModel, bindingConext) {
            var item = value();
            var model = bindingConext.$root;

            if (!model["koHeat"])
                model["koHeat"] = {};

            if (!model["koHeat"][item.context])
                model["koHeat"][item.context] = ko.observable(new HeatContext());

            var ctx = model["koHeat"][item.context];

            if (!isNaN(item.value)) {
                if (+item.value > ctx().max()) ctx().max(+item.value);
                if (+item.value < ctx().min()) ctx().min(+item.value);
                ctx().elements.push(element);
            }

            $(element).html(item.value).data('koHeat', item.value);

            //set value

            function HeatContext() {
                var self = this;
                this.max = ko.observable(0);
                this.min = ko.observable(Number.MAX_VALUE);
                this.elements = ko.observableArray([]);

                this.setHeat = ko.computed(function () {

                    var max = self.max(),
                        min = self.min(),
                        elems = self.elements(),
                        mid = (max + min) / 2,
                        range = max - min,
                        r, g, b, x;

                    for (var i = 0; i < elems.length; i++) {
                        var val = $(elems[i]).data('koHeat');
                        x = (val - mid) / range;
                        if (x > 0) {
                            g = Math.abs(Math.round(x* 100));
                            b = 0.0;
                            r = Math.abs(Math.round((1.0 - x) * 100));
                        } else {
                            g = Math.abs(Math.round(x * 100));
                            b = Math.abs(Math.round((1.0 - x) * 100));
                            r = 0.0;
                        }
                        log(r,g,b);

                        $(elems[i]).css('color', "rgb(" + r + "," + g + "," + b + ")");
                    }
                });
            }
        }
    };

Edit I made up a quick fiddle for the situation

http://jsfiddle.net/rwJfK/3/

Zholen
  • 1,762
  • 2
  • 24
  • 53

1 Answers1

1

Instead of making setHeat a computed, simply declare it as a private function within HeatContext by removing the this. prefix and subscribing to min and max changes like:

this.min.subscribe(setHeat);
this.max.subscribe(setHeat);

Now, setHeat will only run when min or max are updated - and not when an element is pushed to the array.

There are many ways you can improve this binding. I'll try and put together an improved version in a couple days. I'll make an edit to this answer.

Sethi
  • 1,378
  • 8
  • 14
  • I found a solution to my problem before noticing your post, here's a fiddle: http://jsfiddle.net/rwJfK/11/ : I hadn't looked into the subscribe feature of ko, so thanks for that input, but I would still love to see how someone else would make this kind of binding! – Zholen Oct 12 '12 at 19:19
  • I've been trying to rework your fiddle using something like [this](http://stackoverflow.com/questions/12671126/is-there-an-alternative-to-the-ivalueconverter-in-knockout-js/12671798#12671798), because you are setting the html of the element and adding jQuery `.data` which is basically doing KOs job in a more static way. I'm just having a little trouble further down the line because I don't know exactly what you want to do. If you could explain the entire thing I'll take a stab at applying better practices. – Sethi Oct 17 '12 at 09:42