A markup language on top of d3 to combine data and DOM elements.
Currently, all the selection and data API methods from d3 are available.
classed .d3-ml
| ;( function(){ | |
| // an extension for d3 that iterates over Javascript objects | |
| // to build a DOM derived from data. | |
| d3.ml = { | |
| // YAML templates that execute d3ml | |
| templates: {}, | |
| requests: {}, | |
| scripts: {}, | |
| build: | |
| function(s, template, state){ | |
| // for a selection build a template's state | |
| // with available requests, templates, and scripts | |
| if ( !s.data()[0]){ | |
| // make sure the parent selection has | |
| // data for the worker to use. | |
| s = s.datum( {} ); | |
| } | |
| if ( d3.ml.templates[template][state] ){ | |
| // add a class to the parent selection so it knows d3ml was used | |
| s.classed('d3-ml',true); | |
| // build the template only if it exists | |
| return d3.ml.worker( | |
| s, | |
| d3.ml.templates[template][state] | |
| ) | |
| } else { | |
| console.log( 'There is no template, ' + template + ',with the state,' + state +'.' ); | |
| } | |
| }, | |
| worker: | |
| function ( s, template ){ | |
| // Execute a d3ml template on the | |
| // selection | |
| if ( s.data()[0] ){ | |
| // if a selection has data then | |
| // bring it into the scope | |
| var data = s.data()[0]; | |
| } | |
| template.forEach( function(template){ | |
| // for each of selections | |
| // traverse the templates with | |
| // d3ml | |
| s = d3.ml.task( s, d3.entries(template)[0], data) | |
| }) | |
| return s; | |
| }, | |
| helper: { | |
| extend : | |
| function ( d, _d, i ){ | |
| // a hack for $.extend with native d3 | |
| if ( !Array.isArray(d) ){ | |
| d3.merge( [ | |
| d3.entries( d ), | |
| d3.entries( _d ) | |
| ]) | |
| .forEach( function(_d){ | |
| i[ _d.key] = _d.value; | |
| }) | |
| return i; | |
| } else { | |
| // Don't append objects to Arrays | |
| return d; | |
| } | |
| }, | |
| extend: | |
| function ( d, _d, i ){ | |
| // a hack for $.extend with native d3 | |
| if ( !Array.isArray(d) ){ | |
| d3.merge( [ | |
| d3.entries( d ), | |
| d3.entries( _d ) | |
| ]) | |
| .forEach( function(_d){ | |
| i[ _d.key] = _d.value; | |
| }) | |
| return i; | |
| } else { | |
| // Don't append objects to Arrays | |
| return d; | |
| } | |
| }, | |
| intersect : | |
| function (str, set ){ | |
| // return true if a string is in a set of strings | |
| return( | |
| set.filter( function(d){ | |
| return (d == str); | |
| }).length > 0 | |
| ) | |
| }, | |
| reduce: | |
| function ( k, d, _d ){ | |
| // get the value of a key for | |
| // either the current scope ':' or local scope '@' | |
| if (k[0] == '@'){ | |
| // return data from the local scope | |
| d = _d; | |
| } | |
| if ( d3.ml.helper.intersect( k , [':','@'] ) ){ | |
| return d | |
| } else if ( d3.ml.helper.intersect( k[0] , [':','@'] ) ){ | |
| return k.slice(1).split('.') | |
| .reduce( function( p, n){ | |
| if (p[n]){ | |
| // recurse object through intersect | |
| return p[n] | |
| } else { | |
| // default if key doesn't exist | |
| return {} | |
| } | |
| }, d ); | |
| } else { | |
| return k; | |
| } | |
| } | |
| }, | |
| task: function ( s, t, data ){ | |
| // for a selection, s, apply a action defined t | |
| // using data if it is needed | |
| if ( t.key == 'call' ){ | |
| // update selection and the data scope is updated in the worker | |
| s = s[t.key]( function(s){ | |
| d3.ml.worker(s, t.value); | |
| }) | |
| } else if ( t.key == 'template' ){ | |
| // send a nested template to the worker to execute | |
| s = d3.ml.worker( | |
| s, | |
| d3.ml.helper.reduce( | |
| t.value, | |
| d3.ml.templates | |
| ) | |
| ) | |
| } else if ( t.key == 'each' ){ | |
| // iterate over a multi-selection array, d3 selection | |
| s = s[t.key]( function(){ | |
| d3.ml.worker(d3.select(this), t.value); | |
| }) | |
| } else if ( t.key == 'class' ){ | |
| // Append classes to the selection. | |
| // d.value is a object that is iterated over | |
| d3.entries( t.value ) | |
| .forEach( function(_d){ | |
| if (_d.value == null ){ | |
| s.classed( _d.key, true ) | |
| } else { | |
| s.classed( _d.key, _d.value ) | |
| } | |
| }) | |
| } else if ( | |
| d3.ml.helper.intersect( t.key, ['attr','style','property'] ) | |
| ){ | |
| // change the style of attributes | |
| // values are objects like class | |
| d3.entries( t.value ) | |
| .forEach( function(_d){ | |
| s[t.key]( _d.key, function(__d){ | |
| return ( | |
| d3.ml.helper.reduce( _d.value, data, __d ) | |
| ) | |
| }) | |
| }) | |
| } else if ( | |
| d3.ml.helper.intersect( t.key, ['enter','exit','remove'] ) | |
| ){ | |
| // modified data in selections | |
| s = s[t.key]() | |
| } else if ( | |
| d3.ml.helper.intersect( t.key, ['data'] ) | |
| ){ | |
| // update data in for a selection | |
| s = s[t.key]( function(_d){ | |
| return d3.ml.helper.reduce( t.value, data, _d ) | |
| }) | |
| } else if ( | |
| d3.ml.helper.intersect( t.key, ['xml','json','yaml','yml','aml','plain','csv','tsv'] ) | |
| ){ | |
| // append data from the archie base | |
| // update selection | |
| var parse = function(d){ return d;} | |
| if ( d3.ml.helper.intersect( t.key, ['yaml','yml' ] ) ){ | |
| t.key = 'text' | |
| parse = function(d){ | |
| return jsyaml.load(d); | |
| } | |
| } | |
| if ( d3.ml.helper.intersect( t.key, ['aml' ] ) ){ | |
| t.key = 'text' | |
| parse = function(d){ | |
| return archieml.load(d); | |
| } | |
| } | |
| if ( d3.ml.helper.intersect( t.key, ['plain' ] ) ){ | |
| t.key = 'text' | |
| } | |
| d3[t.key]( t.value, function(d){ | |
| if ( t.key == 'aml' ){ | |
| if( window['archieml']){ | |
| d = archieml.load( d ) | |
| } | |
| } | |
| s = d3.ml.task( s, { | |
| key: 'datum', | |
| value: parse(d) | |
| }, data) | |
| }) | |
| } else if ( d3.ml.helper.intersect( t.key, ['datum'] ) ){ | |
| // append data-on-the-fly | |
| s = s[t.key]( function(_d){ | |
| // previously attached data | |
| if (_d ){ | |
| // merge objects | |
| if (typeof t.value == 'string'){ | |
| // access predefined variables | |
| // allows arbitrary data | |
| t.value = d3.ml.helper.reduce( t.value, data, _d) | |
| } | |
| return ( | |
| d3.ml.helper.extend( _d, t.value, {} ) | |
| ) | |
| } else { | |
| // attach data if it hasnt been assigned | |
| return (_d); | |
| } | |
| }) | |
| } else if ( d3.ml.helper.intersect( t.key, ['selectAll','select'] ) ){ | |
| // selection changes | |
| s = s[t.key]( t.value ); | |
| } else if ( d3.ml.helper.intersect( t.key, ['append','insert'] ) ){ | |
| // selection changes | |
| if (t.value[0] == '$'){ | |
| // I wish i knew regular expressions | |
| // this can probably be done with the function update by converting to a template | |
| // $tag.class1-name.class2-name#id | |
| var path = t.value.slice(1).split('.'); | |
| if ( (path[0].length > 0) && ( path[0][0] != '#' ) ){ | |
| // dont forget to update teh selection | |
| s = s.append( path[0] ) | |
| } | |
| // add classes | |
| path.slice(1).filter( function(path){ | |
| return (path[0] != '#') && ( path[0].length > 0 ) | |
| }).forEach( function(path){ | |
| s.classed( path.split('#')[0], true); | |
| }) | |
| path.filter( function(path){ | |
| return (path.split('#')[1]) | |
| }).forEach( function(path){ | |
| // this will only happen once | |
| s.attr( 'id', path.split('#')[1] ); | |
| }) | |
| } else { | |
| // normal append | |
| s = s[t.key]( t.value ); | |
| } | |
| } else if ( d3.ml.helper.intersect( t.key, ['text','html'] ) ){ | |
| // append data from the d3 base | |
| s[t.key]( function(_d){ | |
| return d3.ml.helper.reduce( t.value, data, _d) | |
| }) | |
| } else { | |
| // don't use any other commands yet | |
| } | |
| return s; | |
| } | |
| } | |
| })(); |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <style> | |
| @import "https://cdnjs.cloudflare.com/ajax/libs/materialize/0.95.3/css/materialize.min.css"; | |
| @import "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/codemirror.min.css"; | |
| @import "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/theme/blackboard.css"; | |
| @import "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/addon/fold/foldgutter.css"; | |
| body { | |
| font-size: 12px; | |
| } | |
| #toolbar{ | |
| position: fixed; | |
| right: 0; | |
| top: 0; | |
| } | |
| #toggle { | |
| margin-top: 1.25em; | |
| } | |
| #template { | |
| position: fixed; | |
| right: 0; | |
| width: 400px; | |
| top: 68px; | |
| height: 100%; | |
| opacity: 0.7; | |
| } | |
| .CodeMirror { | |
| height: auto; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="preview" class="row"> | |
| <div class="container"> | |
| <h5>This tool creates webpages by the key stroke.</h5> | |
| <p class="flow-text">Mark up the YAML editor with d3-like commands to build a DOM with inline data.</p> | |
| </div> | |
| </div> | |
| <div id="template" class="mode"></div> | |
| <div id="toolbar" class="col"> | |
| <div class="row"> | |
| <a id="toggle" class="waves-effect waves-light btn col s2"> | |
| <i class="mdi-image-edit"></i> | |
| </a> | |
| <div class="input-field col s10 download"> | |
| <i class="mdi-file-file-download prefix"></i> | |
| <input id="template-url" type="text" class="validate" value="./templates.yml" > | |
| <label for="template-url">Template URL</label> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="//cdn.jsdelivr.net/g/d3js,jquery"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/codemirror.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/mode/yaml/yaml.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/addon/fold/foldcode.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/addon/fold/foldgutter.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/addon/fold/indent-fold.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/addon/fold/comment-fold.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/materialize/0.95.3/js/materialize.min.js"> | |
| </script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/js-yaml/3.2.7/js-yaml.min.js"> | |
| </script> | |
| <script src="d3.ml.js"> | |
| </script> | |
| <script> | |
| ;( function(){ | |
| var toolbarHeight = 68, | |
| showEditor = 1; | |
| var template = d3.select('#template'), | |
| preview = d3.select('#preview'), | |
| templateUrl = d3.select("#template-url"), | |
| win = d3.select(window); | |
| var editor = CodeMirror(template.node(), { | |
| theme: "blackboard", | |
| mode: "yaml", | |
| lineNumbers: true, | |
| lineWrapping: true, | |
| extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}, | |
| foldGutter: { | |
| rangeFinder: new CodeMirror.fold.combine(CodeMirror.fold.indent, CodeMirror.fold.comment) | |
| }, | |
| gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"] | |
| }); | |
| d3.selectAll('#toggle') | |
| .on('click', function(){ | |
| template.style('display', | |
| (showEditor = !showEditor) ? "block" : "none" | |
| ); | |
| }); | |
| editor.on('change', update); | |
| win.on("resize", resize); | |
| templateUrl.on("change", updateHash); | |
| win.on("hashchange", load) | |
| win.on("resize")(); | |
| if(hash()){ | |
| templateUrl.property("value", hash()); | |
| } | |
| templateUrl.on("change")(); | |
| function hash(value){ | |
| if(value){ | |
| window.location.hash = value; | |
| } | |
| return window.location.hash.slice(1); | |
| } | |
| function updateHash(){ | |
| hash(templateUrl.property("value")); | |
| win.on("hashchange")() | |
| } | |
| function load(){ | |
| d3.text(hash(), 'text/yaml', function(d){ | |
| editor.setValue(d); | |
| }); | |
| } | |
| function update(){ | |
| d3.ml.templates = jsyaml.load( editor.getValue() ); | |
| preview.call( function(s){ | |
| s.html('') | |
| d3.entries( d3.ml.templates['display'] ) | |
| .forEach( function(d){ | |
| d3.ml.build( s, d.key, d.value ) | |
| }); | |
| }); | |
| } | |
| function resize(){ | |
| editor.setSize( | |
| null, | |
| (win.node().innerHeight - toolbarHeight) + "px" | |
| ) | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
| display: | |
| card: mount | |
| card-title: | |
| mount: | |
| - call: | |
| - append: $span.card-title | |
| - text: ':title' | |
| - call: | |
| - append: p | |
| - text: ':content' | |
| card-content: | |
| mount: | |
| - append: $div.card-content.white-text | |
| - call: | |
| - template: ':card-title.mount' | |
| card-action: | |
| mount: | |
| - append: $div.card-action | |
| - selectAll: a | |
| - data: | |
| - 1 | |
| - 2 | |
| - call: | |
| - enter: | |
| - append: a | |
| - each: | |
| - text: ':' | |
| - attr: | |
| href: ':' | |
| card: | |
| mount: | |
| - append: $div.row | |
| - append: $div.col.s12.m6 | |
| - append: $div.card.blue.darken-1 | |
| - datum: | |
| title: Title | |
| content: This is the content | |
| - call: | |
| - call: | |
| - template: ':card-content.mount' | |
| - call: | |
| - template: ':card-action.mount' | |