Здравствуйте, это Эйдан Вольф, вице-президент по продукту в Datazar. Сегодня я покажу вам, как создать модуль NotebookJS за 9 минут.

Если вы не знакомы с NotebookJS, это облачная система научных вычислений, полностью построенная на Javascript. Что делает NotebookJS еще более мощным, так это добавление модулей, которые создаются и поддерживаются нашим замечательным сообществом Datazar.

К концу этого руководства ваш модуль будет мгновенно доступен для включения в любую записную книжку и, возможно, внесет свой вклад в новый исследовательский проект, который изменит мир! Разве это не захватывающе?

Начиная

Используя ваш любимый редактор кода, создайте новый файл JavaScript. Сделав это, скопируйте и вставьте этот стартовый код.

//nb.js Tutorial Module
nb.tutorial = function () {};
nb.tutorial.test=function() {return “this module is working!”;};

Если вы хотите, чтобы переменная или функция были доступны за пределами вашего модуля, вы должны сначала включить «nb.[moduleName]». перед ссылкой на объект. В этом примере имя моего модуля — nb.tutorial, и я включил свою первую функцию «nb.tutorial.test». Я мог бы загрузить этот модуль прямо сейчас и вызвать nb.tutorial.test() из своей записной книжки, и он отобразит мое сообщение!

Модули Notebook могут в полной мере использовать D3.js, поэтому для этого руководства мы собираемся создать визуализацию D3, готовую к NotebookJS.

Ради скорости позаимствуем замечательную визуализацию аккордовой диаграммы Стюарта Нойса. http://bl.ocks.org/StewartNoyce/9532973

1. Сначала создайте новую функцию внутри вашего файла.

nb.tutorial.chord = function (data) {
//where we'll put our D3 code!
}

2. Затем скопируйте и вставьте код из ‹script›‹/script› из примера диаграммы аккордов Стюарта Нойса в свой код.

(Для этого урока нам нужна только функция Chord)

function Chord(container, options, matrix) {
        // initialize the chord configuration variables
        var config = {
            width: 640,
            height: 560,
            rotation: 0,
            textgap: 26,
            colors: ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17", "#666666"]
        };
        
        // add options to the chord configuration object
        if (options) {
            extend(config, options);
        }
        
        // set chord visualization variables from the configuration object
        var offset = Math.PI * config.rotation,
            width = config.width,
            height = config.height,
            textgap = config.textgap
            colors = config.colors;
        
        // set viewBox and aspect ratio to enable a resize of the visual dimensions 
        var viewBoxDimensions = "0 0 " + width + " " + height,
            aspect = width / height;
        
        if (config.gnames) {
            gnames = config.gnames;
        } else {
            // make a list of names
            gnames = [];
            for (var i=97; i<matrix.length; i++) {
                gnames.push(String.fromCharCode(i));
            }
        }

        // start the d3 magic
        var chord = d3.layout.chord()
            .padding(.05)
            .sortSubgroups(d3.descending)
            .matrix(matrix);

        var innerRadius = Math.min(width, height) * .31,
            outerRadius = innerRadius * 1.1;

        var fill = d3.scale.ordinal()
            .domain(d3.range(matrix.length-1))
            .range(colors);
    
        var svg = d3.select("body").append("svg")
            .attr("id", "visual")
            .attr("viewBox", viewBoxDimensions)
            .attr("preserveAspectRatio", "xMinYMid")    // add viewBox and preserveAspectRatio
            .attr("width", width)
            .attr("height", height)
          .append("g")
            .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

        var g = svg.selectAll("g.group")
            .data(chord.groups)
          .enter().append("svg:g")
            .attr("class", "group");

        g.append("svg:path")
            .style("fill", function(d) { return fill(d.index); })
            .style("stroke", function(d) { return fill(d.index); })
            .attr("id", function(d, i) { return "group" + d.index; })
            .attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(startAngle).endAngle(endAngle))
            .on("mouseover", fade(.1))
            .on("mouseout", fade(1));

        g.append("svg:text")
            .each(function(d) {d.angle = ((d.startAngle + d.endAngle) / 2) + offset; })
            .attr("dy", ".35em")
            .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
            .attr("transform", function(d) {
                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                    + "translate(" + (outerRadius + textgap) + ")"
                    + (d.angle > Math.PI ? "rotate(180)" : "");
              })
            .text(function(d) { return gnames[d.index]; });

        svg.append("g")
            .attr("class", "chord")
          .selectAll("path")
            .data(chord.chords)
          .enter().append("path")
            .attr("d", d3.svg.chord().radius(innerRadius).startAngle(startAngle).endAngle(endAngle))
            .style("fill", function(d) { return fill(d.source.index); })
            .style("opacity", 1)
          .append("svg:title")
            .text(function(d) { 
                return  d.source.value + " people from " + gnames[d.source.index] + " commute to " + gnames[d.target.index]; 
            });
    
        // helper functions start here
        
        function startAngle(d) {
            return d.startAngle + offset;
        }

        function endAngle(d) {
            return d.endAngle + offset;
        }
        
        function extend(a, b) {
            for( var i in b ) {
                a[ i ] = b[ i ];
            }
        }

        // Returns an event handler for fading a given chord group.
        function fade(opacity) {
            return function(g, i) {
                svg.selectAll(".chord path")
                    .filter(function(d) { return d.source.index != i && d.target.index != i; })
                    .transition()
                    .style("opacity", opacity);
            };
        }
        
        
        window.onresize = function() {
            var targetWidth = (window.innerWidth < width)? window.innerWidth : width;
            
            var svg = d3.select("#visual")
                .attr("width", targetWidth)
                .attr("height", targetWidth / aspect);
        }

        
    }

3. Чтобы этот модуль работал с ноутбуком, мы должны изменить несколько вещей, касающихся размера и стилей.

var config = {
            width: 640,
            height: 560,
};
//changes to
var config = {
            width: nb.boxWidth,
            height: nb.boxHeight,
};

Далее нам нужно добавить .attr(‘class’,’mainBox’) в список атрибутов svg.

var svg = d3.select("body").append("svg")
//changes to 
var svg = d3.select("body").append("svg").attr(‘class’,’mainBox’)

Эти три изменения гарантируют, что визуализатор отображается в поле правильного размера с правильными стилями.

4. Теперь нам нужно подготовить модуль для пользовательского набора данных.

Удалите параметры из Chord() и добавьте «опции» и «матрицу».

Chord = function (options, matrix) {
  • «options» — это массив настраиваемых параметров для таких вещей, как цвет
  • «матрица» — это данные, которые мы хотим визуализировать

Если вы еще этого не сделали, объедините Chord() внутри функции nb.tutorial.chord(), которую мы создали ранее.

nb.tutorial.chord = function (data) {
     Chord(options, matrix) {
          ...
          ...
     }
}

Наш набор данных настроен как файл CSV с рядом заголовков и множеством строк под заголовками. Вот пример набора данных, представляющий количество людей, перемещающихся между Марин, Сонома, Напа и Сан-Франциско.

[ "Marin", "Sonoma", "Napa", "San Francisco"],
    [ 198, 11, 3, 330 ],
    [ 11, 89, 8, 33 ],
    [ 0, 1, 51, 29 ],
    [ 0, 0, 0, 0 ],
]

Чтобы заставить этот набор данных работать, мы сначала должны вытащить заголовки, оставив только матрицу чисел. Мы сохраним названия в chord_options для последующего использования.

nb.tutorial.chord = function (data) {
var chord_options = {
    "gnames": data[0], //captures titles
    "rotation": -0.7,
    "colors": ["#034e7b","#feb24c","#b10026","#238443"]
};
data.splice( 0, 1 ); //removes titles from data
Chord(options, matrix) {
...

'chord_options' в этом примере является статическим, но вы можете сделать его параметром самой функции модуля.

Наконец, мы должны вызвать функцию Chord() из nb.tutorial.chord().

   ...
   Chord(options, matrix) {
      ...
   }
   Chord(chord_options, data);
}

Вот готовый код:

//nb.js Tutorial Module
nb.tutorial = function () {};
nb.tutorial.test=function() {return "this module is working!";};
nb.tutorial.chord = function (data) {
var chord_options = {
    "gnames": data[0],
    "rotation": -0.7,
    "colors": ["#034e7b","#feb24c","#b10026","#238443","#fdbb84","#ffffb2","#fed976"]
};
data.splice( 0, 1 );
function Chord(options, matrix) {
// initialize the chord configuration variables
        var config = {
            width: nb.boxWidth,
            height: nb.boxHeight,
            rotation: 0,
            textgap: 26,
            colors: ["#7fc97f", "#beaed4", "#fdc086", "#ffff99", "#386cb0", "#f0027f", "#bf5b17", "#666666"]
        };
        
        // add options to the chord configuration object
        if (options) {
            extend(config, options);
        }
        
        // set chord visualization variables from the configuration object
        var offset = Math.PI * config.rotation,
            width = config.width,
            height = config.height,
            textgap = config.textgap,
            colors = config.colors;
        
        // set viewBox and aspect ratio to enable a resize of the visual dimensions 
        var viewBoxDimensions = "0 0 " + width + " " + height,
            aspect = width / height;
        
        if (config.gnames) {
            gnames = config.gnames;
        } else {
            // make a list of names
            gnames = [];
            for (var i=97; i<matrix.length; i++) {
                gnames.push(String.fromCharCode(i));
            }
        }
// start the d3 magic
        var chord = d3.layout.chord()
            .padding(0.05)
            .sortSubgroups(d3.descending)
            .matrix(matrix);
var innerRadius = Math.min(width, height) * 0.31,
            outerRadius = innerRadius * 1.1;
var fill = d3.scale.ordinal()
            .domain(d3.range(matrix.length-1))
            .range(colors);
    
        var svg = d3.select("body").append("svg")
            .attr("id", "visual")
            .attr("viewBox", viewBoxDimensions)
            .attr("preserveAspectRatio", "xMinYMid")    // add viewBox and preserveAspectRatio
            .attr("width", width)
            .attr("height", height)
            .attr('class','mainBox')
          .append("g")
            .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll("g.group")
            .data(chord.groups)
          .enter().append("svg:g")
            .attr("class", "group");
g.append("svg:path")
            .style("fill", function(d) { return fill(d.index); })
            .style("stroke", function(d) { return fill(d.index); })
            .attr("id", function(d, i) { return "group" + d.index; })
            .attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(startAngle).endAngle(endAngle))
            .on("mouseover", fade(0.1))
            .on("mouseout", fade(1));
g.append("svg:text")
            .each(function(d) {d.angle = ((d.startAngle + d.endAngle) / 2) + offset; })
            .attr("dy", ".35em")
            .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
            .attr("transform", function(d) {
                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                    + "translate(" + (outerRadius + textgap) + ")"
                    + (d.angle > Math.PI ? "rotate(180)" : "");
              })
            .text(function(d) { return gnames[d.index]; })
            .on("mouseover", fade(0.1))
            .on("mouseout", fade(1));
svg.append("g")
            .attr("class", "chord")
          .selectAll("path")
            .data(chord.chords)
          .enter().append("path")
            .attr("d", d3.svg.chord().radius(innerRadius).startAngle(startAngle).endAngle(endAngle))
            .style("fill", function(d) { return fill(d.source.index); })
            .style("opacity", 1)
          .append("svg:title")
            .text(function(d) { 
                return  d.source.value + " people from " + gnames[d.source.index] + " commute to " + gnames[d.target.index]; 
            });
    
        // helper functions start here
        
        function startAngle(d) {
            return d.startAngle + offset;
        }
function endAngle(d) {
            return d.endAngle + offset;
        }
        
        function extend(a, b) {
            for( var i in b ) {
                a[ i ] = b[ i ];
            }
        }
// Returns an event handler for fading a given chord group.
        function fade(opacity) {
            return function(g, i) {
                svg.selectAll(".chord path")
                    .filter(function(d) { return d.source.index != i && d.target.index != i; })
                    .transition()
                    .style("opacity", opacity);
            };
        } 
    }
Chord(chord_options, data);
};
//Test chord with test data
/*
nb.tutorial.chord(matrix = [ 
    [ "Marin", "Sonoma", "Napa", "San Francisco", "East Bay", "Silicon Valley", "Remote Locations" ],
    [ 198, 11, 3, 330, 32, 35, 59 ],
    [ 11, 89, 8, 33, 15, 0, 28 ],
    [ 0, 1, 51, 29, 17, 0, 26 ],
    [ 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0, 0, 0 ]
]);
*/

5. Теперь пришло время загрузить ваш модуль в Datazar.

Переименуйте файл .js в .nb.js. Этот шаг необязателен, но рекомендуется и может оказаться важным в будущих версиях Notebook.

Затем перейдите на https://www.datazar.com/notebook/modules/ и нажмите Создать модуль, чтобы начать процесс загрузки.

После заполнения основной информации во всплывающем окне вы должны быть перенаправлены на этот экран. В окне вы можете:

  • Предоставить документацию (в Markdown)
  • Сохраняйте исходный код
  • Загрузите пользовательский значок (нажав на темно-серый квадрат)
  • Редактируйте исходный код прямо в редакторе кода Datazar.

Вот моя страница модуля с пользовательским значком и документацией.

Последний шаг — загрузить фактический код, поэтому нажмите кнопку загрузки в правом верхнем углу и выберите файл nb.js. После загрузки модуль готов к включению в любой сеанс Notebook.

Ура! Ваш самый первый модуль! Поздравляем!

Чтобы использовать свой модуль в Notebook, просто нажмите «Добавить» и введите имя, которое вы дали своему модулю ранее. Я назвал свой модуль «tutorialModule», поэтому все, что мне нужно сделать, это ввести его и нажать «Включить модули».

Теперь просто вызовите функцию аккорда в редакторе слева и наблюдайте, как диаграмма аккорда волшебным образом появляется в окне справа.

Если вам понравился этот контент, сообщите нам об этом, нажав кнопку «Рекомендовать» и оставив ответ, сообщив нам, что вы думаете.

Удачи со всеми вашими будущими модулями ноутбука :)