Search code examples
javascripttime-serieslegendrickshaw

How can I make a Rickshaw legend with multiple series enaber/disabler?


I need to make a Rickshaw legend where I can make groups of time series.

e.g. I have 20 time series displayed in a graph and I want them to be grouped in 4 groups, named series1, series2, series3, series4. Series1 would contain the first 5 time series in the graph, series2 would contain the 6th to the 10th and so on.

Before I try to add a custom object in my .js to solve my problem someone knows if there is a built in functionality that I couldn't find?


Solution

  • I didn't find anything helpful so I created a personalized version of a multiple legend that works along with the traditional legend. Unfortunately to update the multiple legend when the standard legend is modified I had to create a myLegend object instead of the default one.

    You can use this version in this way:

    //you must have a traditional Rickshaw seriesData
    
            var graph = new Rickshaw.Graph( {
                        element: document.getElementById("chart"),
                        width: 960,
                        height: 500,
                        renderer: 'line',
                        series: seriesData
                    } );
    
                    graph.render();
    
                    var legend = new Rickshaw.Graph.Legend( {
                        graph: graph,
                        element: document.getElementById('legend')
                    } );
    
                    var mLegend = new multiLegend( {
                        graph: graph,
                        element: document.getElementById('multi_legend'),
                        multiDivision: [
                            {
                                name: "Africa",
                                indexes: [ 0, 1, 2, 3, 4, 5, 6, 7 ]
                            }
                            {
                                name: "Asia",
                                indexes: [ 8, 9, 10, 11, 12 ]
                            },
                            {
                                name: "Europe",
                                indexes: [ 13, 14, 15, 16, 17]
                            },
                            {
                                name: "North America",
                                indexes: [ 18, 19, 20 ]
                            }
                        ]
                    } );
    
                    new myToggle( {
                        graph: graph,
                        legend: legend,
                        multiLegend: mLegend
                    } );
    
                    new multiToggle( {
                        graph: graph,
                        multiLegend: mLegend,
                        legend: legend
                    });
    

    Here's the code:

        multiLegend = Rickshaw.Class.create( {
    
        className: 'rickshaw_legend',
    
        initialize: function(args) {
            this.element = args.element;
            this.graph = args.graph;
            this.naturalOrder = args.naturalOrder;
            this.seriesGroups = args.multiDivision;
    
            this.element.classList.add(this.className);
    
            this.list = document.createElement('ul');
            this.element.appendChild(this.list);
    
            this.render();
    
            // we could bind this.render.bind(this) here
            // but triggering the re-render would lose the added
            // behavior of the series toggle
            this.graph.onUpdate( function() {} );
        },
    
        render: function() {
            var self = this;
    
            while ( this.list.firstChild ) {
                this.list.removeChild( this.list.firstChild );
            }
            this.lines = [];
    
            var allSeries = this.graph.series;
    
            self.seriesGroups.forEach( function(s) {
                var series = allSeries.filter( function(value, index) {
                        return (s.indexes.indexOf(index)!=-1) ? true : false;
                });
                series = series.reverse();
                self.addLine(s.name, series);
            } );
    
    
        },
    
        addLine: function (name, series) {
            var line = document.createElement('li');
            line.className = 'line';
            if (series.disabled) {
                line.className += ' disabled';
            }
            if (series.className) {
                d3.select(line).classed(series.className, true);
            }
            var swatch = document.createElement('div');
            swatch.className = 'swatch';
            swatch.style.backgroundColor = "#0000FF";
    
            line.appendChild(swatch);
    
            var label = document.createElement('span');
            label.className = 'label';
            label.innerHTML = name;
    
            line.appendChild(label);
            this.list.appendChild(line);
    
            line.series = series;
    
            var _line = { element: line, series: series, disabled: false};
            if (this.shelving) {
                this.shelving.addAnchor(_line);
                this.shelving.updateBehaviour();
            }
            if (this.highlighter) {
                this.highlighter.addHighlightEvents(_line);
            }
            this.lines.push(_line);
            return line;
        }
    } );
    
    
    multiToggle = function(args) {
    
        this.graph = args.graph;
        this.multiLegend = args.multiLegend;
        this.legend = args.legend;
    
        var self = this;
    
        this.addAnchor = function(line) {
    
            var anchor = document.createElement('a');
            anchor.innerHTML = '✔';
            anchor.classList.add('action');
            line.element.insertBefore(anchor, line.element.firstChild);
    
            anchor.onclick = function(e) {
                if (line.disabled) {
                    line.series.forEach( function(serie) {
                        serie.enable();
                    });
                    line.element.classList.remove('disabled');
                    line.disabled = false;
                    self.legend.lines
                        .filter(function(value) {
                            return (line.series.indexOf(value.series)!=-1) ? true : false;
                        })
                        .forEach( function(l) {
                            l.element.classList.remove('disabled');
                            l.disabled = false;
                        });
                } else {
                    if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
                    line.series.forEach( function(serie) {
                        serie.disable();
                    });
                    line.element.classList.add('disabled');
                    line.disabled = true;
                    self.legend.lines
                        .filter(function(value) {
                            return (line.series.indexOf(value.series)!=-1) ? true : false;
                        })
                        .forEach( function(l) {
                            l.element.classList.add('disabled');
                            l.disabled = true;
                        });
                }
    
                self.graph.update();
    
            }.bind(this);
    
            var label = line.element.getElementsByTagName('span')[0];
            label.onclick = function(e){
    
                var disableAllOtherLines = line.disabled;
                if ( ! disableAllOtherLines ) {
                    for ( var i = 0; i < self.multiLegend.lines.length; i++ ) {
                        var l = self.multiLegend.lines[i];
                        if ( line.series === l.series ) {
                            // noop
                        } else if ( l.series.disabled ) {
                            // noop
                        } else {
                            disableAllOtherLines = true;
                            break;
                        }
                    }
                }
    
                // show all or none
                if ( disableAllOtherLines ) {
    
                    // these must happen first or else we try ( and probably fail ) to make a no line graph
                    line.series.forEach( function(serie) {
                        serie.enable();
                    });
                    line.element.classList.remove('disabled');
                    line.disabled = false;
                    self.legend.lines
                        .filter(function(value) {
                            return (line.series.indexOf(value.series)!=-1) ? true : false;
                        })
                        .forEach( function(l) {
                            l.element.classList.remove('disabled');
                            l.disabled = false;
                        });
    
                    self.multiLegend.lines.forEach(function(l){
                        if ( line.series === l.series ) {
                            // noop
                        } else {
                            l.series.forEach( function(serie) {
                                serie.disable();
                            });
                            l.element.classList.add('disabled');
                            l.disabled = true;
                            self.legend.lines
                                .filter(function(value) {
                                    return (l.series.indexOf(value.series)!=-1) ? true : false;
                                })
                                .forEach( function(l2) {
                                    l2.element.classList.add('disabled');
                                    l2.disabled = true;
                                });
                        }
                    });
    
                } else {
    
                    self.multiLegend.lines.forEach(function(l){
                        l.series.forEach( function(serie) {
                            serie.enable();
                        });
                        l.element.classList.remove('disabled');
                        l.disabled = false;
                        self.legend.lines
                            .filter(function(value) {
                                return (l.series.indexOf(value.series)!=-1) ? true : false;
                            })
                            .forEach( function(l2) {
                                l2.element.classList.remove('disabled');
                                l2.disabled = false;
                            });
                    });
    
                }
    
                self.graph.update();
    
            };
    
        };
    
        if (this.multiLegend) {
    
            var $ = jQuery;
            if (typeof $ != 'undefined' && $(this.multiLegend.list).sortable) {
    
                $(this.multiLegend.list).sortable( {
                    start: function(event, ui) {
                        ui.item.bind('no.onclick',
                            function(event) {
                                event.preventDefault();
                            }
                        );
                    },
                    stop: function(event, ui) {
                        setTimeout(function(){
                            ui.item.unbind('no.onclick');
                        }, 250);
                    }
                });
            }
    
            this.multiLegend.lines.forEach( function(l) {
                self.addAnchor(l);
            } );
        }
    
        this._addBehavior = function() {
    
            this.graph.series.forEach( function(s) {
    
                s.disable = function() {
    
                    if (self.graph.series.length <= 1) {
                        throw('only one series left');
                    }
    
                    s.disabled = true;
                };
    
                s.enable = function() {
                    s.disabled = false;
                };
            } );
        };
        this._addBehavior();
    
        this.updateBehaviour = function () { this._addBehavior() };
    
    };
    
    myToggle = function(args) {
    
        this.graph = args.graph;
        this.legend = args.legend;
        this.multiLegend = args.multiLegend;
    
        var self = this;
    
        this.addAnchor = function(line) {
    
            var anchor = document.createElement('a');
            anchor.innerHTML = '&#10004;';
            anchor.classList.add('action');
            line.element.insertBefore(anchor, line.element.firstChild);
    
            anchor.onclick = function(e) {
                if (line.series.disabled) {
                    line.series.enable();
                    line.element.classList.remove('disabled');
                } else {
                    if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
                    line.series.disable();
                    line.element.classList.add('disabled');
                    self.multiLegend.lines.forEach( function(l) {
                        if(l.series.indexOf(line.series)!=-1) {
                            l.element.classList.add('disabled');
                            l.disabled = true;
                        }
                    });
                }
    
                self.graph.update();
    
            }.bind(this);
    
            var label = line.element.getElementsByTagName('span')[0];
            label.onclick = function(e){
    
                var disableAllOtherLines = line.series.disabled;
                if ( ! disableAllOtherLines ) {
                    for ( var i = 0; i < self.legend.lines.length; i++ ) {
                        var l = self.legend.lines[i];
                        if ( line.series === l.series ) {
                            // noop
                        } else if ( l.series.disabled ) {
                            // noop
                        } else {
                            disableAllOtherLines = true;
                            break;
                        }
                    }
                }
    
                // show all or none
                if ( disableAllOtherLines ) {
    
                    // these must happen first or else we try ( and probably fail ) to make a no line graph
                    line.series.enable();
                    line.element.classList.remove('disabled');
    
                    self.legend.lines.forEach(function(l){
                        if ( line.series === l.series ) {
                            // noop
                        } else {
                            l.series.disable();
                            l.element.classList.add('disabled');
                        }
                    });
    
                    self.multiLegend.lines.forEach( function(l) {
                        l.element.classList.add('disabled');
                        l.disabled = true;
                    });
    
                } else {
    
                    self.legend.lines.forEach(function(l){
                        l.series.enable();
                        l.element.classList.remove('disabled');
                    });
    
                }
    
                self.graph.update();
    
            };
    
        };
    
        if (this.legend) {
    
            var $ = jQuery;
            if (typeof $ != 'undefined' && $(this.legend.list).sortable) {
    
                $(this.legend.list).sortable( {
                    start: function(event, ui) {
                        ui.item.bind('no.onclick',
                            function(event) {
                                event.preventDefault();
                            }
                        );
                    },
                    stop: function(event, ui) {
                        setTimeout(function(){
                            ui.item.unbind('no.onclick');
                        }, 250);
                    }
                });
            }
    
            this.legend.lines.forEach( function(l) {
                self.addAnchor(l);
            } );
        }
    
        this._addBehavior = function() {
    
            this.graph.series.forEach( function(s) {
    
                s.disable = function() {
    
                    if (self.graph.series.length <= 1) {
                        throw('only one series left');
                    }
    
                    s.disabled = true;
                };
    
                s.enable = function() {
                    s.disabled = false;
                };
            } );
        };
        this._addBehavior();
    
        this.updateBehaviour = function () { this._addBehavior() };
    
    };