view src/DML/MainVisBundle/Resources/assets/marionette/modules/GraphicsRenderingModule/GraphicsRenderingModule.20-Renderer.chord-seq.parallel-coordinates.js @ 0:493bcb69166c

added public content
author Daniel Wolff
date Tue, 09 Feb 2016 20:54:02 +0100
parents
children
line wrap: on
line source
"use strict";

App.module("GraphicsRenderingModule", function(GraphicsRenderingModule, App, Backbone, Marionette, $, _, Logger) {

    GraphicsRenderingModule.addInitializer(function(options) {

        GraphicsRenderingModule.registerRenderer({
            id: "chord-seq.parallel-coordinates",
            inherit: "chord-seq._",

            defaultVegaConfig: {
                sizeOfStepMark: 5,
                stepCount: 3,
                primaryAxisFontSize: 11,
                chordGapSizeToChordSizeRatio: 1,
                paddingWhenAxisLabelsAreHidden:         {"top":  5, "left": 15, "bottom": 10, "right": 15},
                paddingWhenAxisLabelsAreShownParitally: {"top": 20, "left": 15, "bottom": 10, "right": 15},
                paddingWhenAxisLabelsAreShown:          {"top":NaN, "left": 15, "bottom": 10, "right": 15},
            },

            _formVC: function(vc, data) {
                var renderer = this;

                // Derive variables from the config
                vc.numberOfRootNotes = vc.sequenceOfUsedRootNotes.length; // Excluding N (if any)
                vc.numberOfChordTypes = vc.sequenceOfUsedChordTypes.length;

                // titles of root notes
                vc.titlesForChordTypes = vc.chordTypesWithM;
                vc.titlesForRootNotes = vc.relativeRootNotes;
                if (vc.recordingsInMajorModeAreIncluded && !vc.recordingsInMinorModeAreIncluded) {
                    vc.titlesForRootNotes = vc.relativeRootNotesInMajor;
                }
                if (vc.recordingsInMinorModeAreIncluded && !vc.recordingsInMajorModeAreIncluded) {
                    vc.titlesForRootNotes = vc.relativeRootNotesInMinor;
                }

                // calculate the sizes of the elements
                vc.numberOfChords = vc.numberOfRootNotes * vc.numberOfChordTypes;
                vc.numberOfGaps   = vc.chordGrouppingIsByType
                        ? vc.numberOfChordTypes - 1
                        : vc.numberOfRootNotes - 1;

                vc.padding = vc.paddingWhenAxisLabelsAreHidden;
                vc.width = vc.totalWidth - vc.padding.left - vc.padding.right;
                vc.labelsForGroupsAreShown = vc.width / (vc.numberOfGaps + 1) > 30;
                vc.labelsForChordsAreShown = vc.width / (vc.numberOfChords + vc.numberOfGaps) > 10;

                if (vc.labelsForGroupsAreShown) {
                    vc.padding = vc.paddingWhenAxisLabelsAreShownParitally;
                }
                if (vc.labelsForChordsAreShown) {
                    vc.padding = vc.paddingWhenAxisLabelsAreShown;
                    var titles = [];
                    if (vc.chordGrouppingIsByType) {
                        titles = _.map(vc.sequenceOfUsedRootNotes, function(index) {return vc.titlesForRootNotes[index];});
                    } else {
                        titles = _.map(vc.sequenceOfUsedChordTypes, function(index) {return vc.titlesForChordTypes[index];});
                    }
                    var maxTitleLength = 0;
                    _.each(titles, function(title) {
                        if (title.length > maxTitleLength) {
                            maxTitleLength = title.length;
                        }
                    });

                    vc.padding.top = maxTitleLength * 4 + 25;
                }

                if (vc.nIsIncluded) {
                    vc.numberOfChords += 1;
                    vc.numberOfGaps   += 1;
                }


                vc.chordSize = 1;
                do {
                    vc.chordSize++;
                    vc.chordGroupGapSize = Math.round(vc.chordSize * vc.chordGapSizeToChordSizeRatio);
                    vc.width = (vc.numberOfChords - 1) * vc.chordSize + vc.numberOfGaps * vc.chordGroupGapSize;
                } while (vc.width + vc.padding.left + vc.padding.right < vc.totalWidth);

                --vc.chordSize;
                vc.chordGroupGapSize = Math.round(vc.chordSize * vc.chordGapSizeToChordSizeRatio);
                vc.width = (vc.numberOfChords - 1) * vc.chordSize + vc.numberOfGaps * vc.chordGroupGapSize;
                vc.height = vc.width + vc.padding.left + vc.padding.right - vc.padding.top -  vc.padding.bottom;


                // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                // Data

                // .............................................................
                // Data - links
                var linksInVegaData = [];
                var encodedChordSequences = data.self.stats.sequences;
                var support = data.self.stats.support;

                var sequenceId = 0;
                _.each(encodedChordSequences, function(encodedChordSequence, i) {
                    var recordsToAdd = [];
                    var failed = false;
                    var chordSequenceHasCycles = _.unique(encodedChordSequence).length !== encodedChordSequence.length;
                    if (!vc.chordSequencesWithCyclesAreIncluded && chordSequenceHasCycles) {
                        return;
                    }

                    var parsedEncodedChordSequence = [];
                    var chordTitles = [];
                    _.each(encodedChordSequence, function(encodedChord, index) {
                        if (index > vc.stepCount) {
                            return;
                        }
                        var parsedEncodedChord = renderer.parseEncodedChord(encodedChord);
                        parsedEncodedChordSequence.push(parsedEncodedChord);
                        if (index == vc.stepCount) {
                            chordTitles.push("...");
                        } else {
                            chordTitles.push(renderer.titleOfParsedEncodedChord(vc, parsedEncodedChord));
                        }
                    });

                    var tooltip = chordTitles.join(" → ") + "<br/>support: " + support[i];
                    _.each(parsedEncodedChordSequence, function(parsedEncodedChord, indexInSequence) {
                        if (failed) {
                            return;
                        }
                        if (parsedEncodedChord !== 0 && !((vc.recordingsInMajorModeAreIncluded && parsedEncodedChord[0] == 1)
                           || (vc.recordingsInMinorModeAreIncluded && parsedEncodedChord[0] == 2))) {
                            failed = true;
                            return;
                        }
                        var bin = renderer.parsedEncodedChordToBin(vc, parsedEncodedChord);
                        if (bin === null) {
                            failed = true;
                            return;
                        }

                        recordsToAdd.push({
                            sequenceId: sequenceId,
                            encodedChordSequence: encodedChordSequence,
                            chordSequenceHasCycles: chordSequenceHasCycles,
                            indexInSequence: indexInSequence,
                            support: support[i],
                            chordCoordinate: renderer.chordBinToCoordinate(vc, bin),
                            color: vc.primaryColor,
                            tooltip: tooltip
                        });
                    });
                    if (!failed) {
                        linksInVegaData.push.apply(linksInVegaData, recordsToAdd);
                    }
                    ++sequenceId;
                });

                // Put most frequent on top
                linksInVegaData.reverse();

                vc.data.push({
                        "name": "links",
                        "values": linksInVegaData
                    });

                // .............................................................
                // Data - chord names in the secondary axis
                var chordsInVegaData = [];
                _.each(vc.sequenceOfUsedRootNotes, function(indexOfRootNote) {
                   _.each(vc.sequenceOfUsedChordTypes, function(indexOfChordType) {
                       var bin = renderer.parsedEncodedChordToBin(vc, [0, indexOfRootNote, indexOfChordType]);
                       chordsInVegaData.push({
                           rootNoteIndex: indexOfRootNote,
                           rootNoteTitle: vc.titlesForRootNotes[indexOfRootNote],

                           chordTypeIndex: indexOfChordType,
                           chordTypeTitle: vc.titlesForChordTypes[indexOfChordType],

                           chordCoordinate: renderer.chordBinToCoordinate(vc, bin)
                       });
                   });
                });
                vc.data.push({
                    "name": "chords",
                    "values": chordsInVegaData,
                });

                // .............................................................
                // Data - chord names in the primary axis
                if (vc.labelsForGroupsAreShown) {
                    var groupsForVega = [];
                    if (vc.chordGrouppingIsByType) {
                        groupsForVega = chordsInVegaData.filter(function(chord) {
                            return chord.rootNoteIndex == vc.sequenceOfUsedRootNotes[0];
                        });
                    } else {
                        groupsForVega = chordsInVegaData.filter(function(chord) {
                            return chord.chordTypeIndex == vc.sequenceOfUsedChordTypes[0];
                        });
                    }
                    vc.data.push({
                        "name": "groups",
                        "values": groupsForVega,
                    });
                }

                // .............................................................
                // Data - steps
                var stepsInVegaData = _.range(0, vc.stepCount);
                vc.data.push({
                    "name": "steps",
                    "values": stepsInVegaData,
                });


                // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                // Scales

                // .............................................................
                // Scale - steps
                vc.scales.push({
                    //"type": "ordinal",
                    "name": "indexInSequence",
                    "domainMin": 0,
                    "domainMax": vc.stepCount - 1,
                    "point": true,
                    "round": true,
                    "range": [0, vc.height]
                });

                // .............................................................
                // Scale - line opacity
                vc.scales.push({
                    "name": "strokeOpacity",
                    "type": "linear",
                    "domain": [0, data.self.coverage["ok_count"] * 2],
                    "point": true,
                    "range": [0, 1]
                });


                // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                // Marks

                // .............................................................
                // Mark - vertical lines for chords
                if (true || vc.guidesAreVisible) {
                    vc.marks.push({
                        "type": "rect",
                        "from": {"data": "chords"},
                        "properties": {
                            "enter": {
                                "x": {"field": "chordCoordinate"},
                                "y": {"value": 0},
                                "width": {"value": 1},
                                "height": {"field": {"group": "height"}},
                                "fill": {"value": vc.colorForAxes}
                            },
                        }
                    });
                }

                // .............................................................
                // Mark - vertical lines for N chord
                if (vc.nIsIncluded && vc.guidesAreVisible) {
                    vc.marks.push({
                        "type": "rect",
                        "properties": {
                            "enter": {
                                "x": {"field": {"group": "width"}},
                                "y": {"value": 0},
                                "width": {"value": "1"},
                                "height": {"field": {"group": "height"}},
                                "fill": {"value": vc.colorForAxes},
                            },
                        }
                    });
                }

                // .............................................................
                // Mark - horisontal lines denoting steps
                if (vc.guidesAreVisible) {
                    for (var right = 0; right < 2; right++) {
                        vc.marks.push({
                            "type": "rect",
                            "from": {"data": "steps"},
                            "properties": {
                                "enter": {
                                    "x": right ? {"field": {"group": "width"}} : {"value": -vc.sizeOfStepMark},
                                    "width": {"value": vc.sizeOfStepMark},
                                    "y": {"scale": "indexInSequence", "field": "data"},
                                    "height": {"value": 1},
                                    "fill": {"value": vc.colorForAxes},
                                }
                            }
                        });
                    }
                }

                // .............................................................
                // Mark - links
                vc.marks.push({
                    "type": "group",
                    "from": {
                        "data": "links",
                        "transform": [ {
                            "type": "facet",
                            "groupby": ["sequenceId"]
                        } ]
                    },
                    "marks": [{
                        "type": "line",
                        "properties": {
                            "enter": {
                                "x": {"field": "chordCoordinate"},
                                "y": {"scale": "indexInSequence", "field": "indexInSequence"},
                                "strokeOpacity": {"scale": "strokeOpacity", "field": "support"},
                                "stroke": {"field": "color"},
                                "strokeWidth": {"value": 2},
                            },
                            "update": {
                                "strokeOpacity": {"scale": "strokeOpacity", "field": "support"},
                                "stroke": {"field": "color"},
                            },
                            "hover": {
                                "strokeOpacity": {"value": 1},
                                "stroke": {"value": "#000"},
                            }
                        },
                    }]
                });

                // .............................................................
                // Mark - group name (primary axis)
                vc.yOffsetForGroupLabels = -vc.padding.top + 15;
                vc.yOffsetForChordLabels = -3;

                vc.xOffsetForGroupLabels = 0.5 * vc.chordSize * ((vc.chordGrouppingIsByType ? vc.numberOfRootNotes : vc.numberOfChordTypes) - 1);

                if (vc.labelsForGroupsAreShown) {
                    vc.marks.push({
                        "type": "text",
                        "from": {"data": "groups"},
                        "properties": {
                            "enter": {
                                "x": {"field": "chordCoordinate", "offset": vc.xOffsetForGroupLabels},
                                "y": {"value": vc.yOffsetForGroupLabels},
                                "text": {"field": vc.chordGrouppingIsByType ? "chordTypeTitle": "rootNoteTitle"},
                                "baseline": {"value":"bottom"},
                                "align": {"value": "center"},
                                "fill": {"value": vc.colorForAxisLabels},
                                "font": {"value": vc.fontFace},
                                "fontSize": {"value": vc.fontSizeForLabelsInAxis},
                            },
                        }
                    });
                }

                // .............................................................
                // Mark - column name for N
                if (vc.nIsIncluded && vc.labelsForGroupsAreShown) {
                    vc.marks.push({
                        "type": "text",
                        "properties": {
                            "enter": {
                                "y": {"value": vc.labelsForChordsAreShown ? vc.yOffsetForChordLabels : vc.yOffsetForGroupLabels},
                                "x": {"field": {"group": "width"}},
                                "text": {"value":"N"},
                                "align": {"value":"center"},
                                "baseline": {"value":"bottom"},
                                "fill": {"value": vc.colorForAxisLabels},
                                "font": {"value": vc.fontFace},
                                "fontSize": {"value": vc.fontSizeForLabelsInAxis},
                            },
                        }
                    });
                }

                // .............................................................
                // Mark - chord name (secondary axis)
                if (vc.labelsForChordsAreShown) {
                    vc.marks.push({
                        "type": "text",
                        "from": {"data": "chords"},
                        "properties": {
                            "enter": {
                                "x": {"field": "data.chordCoordinate", "offset": 0},
                                "y": {"value": vc.yOffsetForChordLabels},
                                "text": {"field": vc.chordGrouppingIsByType ? "data.rootNoteTitle": "data.chordTypeTitle"},
                                "angle": {"value": "-90"},
                                "baseline": {"value": "middle"},
                                "fill": {"value": vc.colorForAxisLabels},
                                "font": {"value": vc.fontFace},
                                "fontSize": {"value": vc.fontSizeForLabelsInSecondaryAxis},
                            },
                        }
                    });
                }

                // .............................................................
                // Mark - fader
                vc.marks.push({
                    "type": "image",
                    "from": {"data": "dummy"},
                    "properties": {
                        "enter": {
                            "x": {"value": 0},
                            "width": {"field": {"group": "width"}},
                            "y": {"field": {"group": "height"}},
                            "height": {"value": vc.padding.bottom + 2},
                            "url": {"value": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQIAAAAMCAYAAACJMrOUAAABFUlEQVRoge2PwW0CQQxFXQEUAoVAYaGT0Ek64Lq5QgMgLVJ4uezBspYsoxnCsPMPT1/y2B4/A5aOhctFqD3ynkvq/tL3yF/+TfobsAZWA2uXj7AK85G4L/bn/pc6X3qf/OU/C38DtglsXG5CrUZK3yt/+c/S34BdBh8ux/A9Y/1T77n7no385T8LfwM+/2Dvch9qJfpT53P3pyJ/+Tfhb8CXEKJtDDgIIdrGgA74vkPnsgu1V5B7T+q8/OXfhL8Bp3/k6PIYai0gf/lX6W/AGbgMnF2OcQn9kTgf+1Pfp/6f2pd7n/zl34S/AT1wHehd9qE29h6Z6s/dF6n9XvnL/y38Dfhx3FzeQq0Ez95f+33yl3+V/r9d8S8/zvVSHwAAAABJRU5ErkJggg=="},
                            "fill": {"value": "#F00"},
                        },
                    }
                });
            }
        });
    });
}, Logger);