- 1 :
// assuming Bubblelines library is loaded by containing page (via voyant.jsp)
- 2 :
/**
- 3 :
* Bubbles is a playful visualization of term frequencies by document.
- 4 :
*
- 5 :
* @example
- 6 :
*
- 7 :
* let config = {
- 8 :
* audio: false, // whether or not to include audio
- 9 :
* docIndex: 1, // document index to restrict to (can be comma-separated list)
- 10 :
* speed: 10, // speed of the animation (0 to 60 lower is slower)
- 11 :
* stopList: null, // a named stopword list or comma-separated list of words
- 12 :
* };
- 13 :
*
- 14 :
* loadCorpus("austen").tool("bubbles", config);
- 15 :
*
- 16 :
* @class Bubbles
- 17 :
* @tutorial bubbles
- 18 :
* @memberof Tools
- 19 :
*/
- 20 :
Ext.define('Voyant.panel.Bubbles', {
- 21 :
extend: 'Ext.panel.Panel',
- 22 :
mixins: ['Voyant.panel.Panel'],
- 23 :
alias: 'widget.bubbles',
- 24 :
statics: {
- 25 :
i18n: {
- 26 :
},
- 27 :
api: {
- 28 :
/**
- 29 :
* @memberof Tools.Bubbles
- 30 :
* @instance
- 31 :
* @property {stopList}
- 32 :
* @default
- 33 :
*/
- 34 :
stopList: 'auto',
- 35 :
- 36 :
/**
- 37 :
* @memberof Tools.Bubbles
- 38 :
* @instance
- 39 :
* @property {docIndex}
- 40 :
*/
- 41 :
docIndex: 0,
- 42 :
- 43 :
/**
- 44 :
* @memberof Tools.Bubbles
- 45 :
* @instance
- 46 :
* @property {limit}
- 47 :
* @default
- 48 :
*/
- 49 :
limit: 100,
- 50 :
- 51 :
/**
- 52 :
* @memberof Tools.Bubbles
- 53 :
* @instance
- 54 :
* @property {Boolean} audio Whether or not to play audio
- 55 :
* @default
- 56 :
*/
- 57 :
audio: false,
- 58 :
- 59 :
/**
- 60 :
* @memberof Tools.Bubbles
- 61 :
* @instance
- 62 :
* @property {Number} speed How fast to play the visualization
- 63 :
* @default
- 64 :
*/
- 65 :
speed: 30
- 66 :
- 67 :
- 68 :
},
- 69 :
glyph: 'xf06e@FontAwesome'
- 70 :
},
- 71 :
config: {
- 72 :
options: {xtype: 'stoplistoption'},
- 73 :
audio: false
- 74 :
},
- 75 :
- 76 :
corpusLoaded: false,
- 77 :
processingLoaded: false,
- 78 :
bubblesAppCode: undefined,
- 79 :
- 80 :
bubbles: undefined,
- 81 :
oscillator: undefined,
- 82 :
gainNode: undefined,
- 83 :
- 84 :
- 85 :
constructor: function() {
- 86 :
- 87 :
this.mixins['Voyant.util.Localization'].constructor.apply(this, arguments);
- 88 :
Ext.apply(this, {
- 89 :
title: this.localize('title'),
- 90 :
html: '<canvas style="width: 100%; height: 100%"></canvas>',
- 91 :
dockedItems: [{
- 92 :
dock: 'bottom',
- 93 :
xtype: 'toolbar',
- 94 :
overflowHandler: 'scroller',
- 95 :
items: [{
- 96 :
xtype: 'documentselectorbutton',
- 97 :
singleSelect: true
- 98 :
},{
- 99 :
xtype: 'slider',
- 100 :
fieldLabel: this.localize('speed'),
- 101 :
labelAlign: 'right',
- 102 :
labelWidth: 40,
- 103 :
width: 100,
- 104 :
increment: 1,
- 105 :
minValue: 1,
- 106 :
maxValue: 60,
- 107 :
value: 30,
- 108 :
listeners: {
- 109 :
render: function(cmp) {
- 110 :
cmp.setValue(parseInt(this.getApiParam("speed")));
- 111 :
if (this.bubbles) {this.bubbles.frameRate(cmp.getValue())}
- 112 :
this.setAudio(cmp.getValue());
- 113 :
Ext.tip.QuickTipManager.register({
- 114 :
target: cmp.getEl(),
- 115 :
text: this.localize('speedTip')
- 116 :
});
- 117 :
- 118 :
},
- 119 :
beforedestroy: function(cmp) {
- 120 :
Ext.tip.QuickTipManager.unregister(cmp.getEl());
- 121 :
},
- 122 :
changecomplete: function(cmp, val) {
- 123 :
this.setApiParam('speed', val);
- 124 :
if (this.bubbles) {this.bubbles.frameRate(val)}
- 125 :
},
- 126 :
scope: this
- 127 :
}
- 128 :
},{
- 129 :
xtype: 'checkbox',
- 130 :
boxLabel: this.localize('sound'),
- 131 :
listeners: {
- 132 :
render: function(cmp) {
- 133 :
cmp.setValue(this.getApiParam("audio")===true || this.getApiParam("audio")=="true");
- 134 :
this.setAudio(cmp.getValue());
- 135 :
Ext.tip.QuickTipManager.register({
- 136 :
target: cmp.getEl(),
- 137 :
text: this.localize('soundTip')
- 138 :
});
- 139 :
- 140 :
},
- 141 :
beforedestroy: function(cmp) {
- 142 :
Ext.tip.QuickTipManager.unregister(cmp.getEl());
- 143 :
},
- 144 :
change: function(cmp, val) {
- 145 :
this.setApiParam('audio', val);
- 146 :
this.setAudio(val);
- 147 :
},
- 148 :
scope: this
- 149 :
}
- 150 :
},{xtype: 'tbfill'}, {
- 151 :
xtype: 'tbtext',
- 152 :
html: this.localize('adaptation') //https://www.m-i-b.com.ar/letters/en/
- 153 :
}]
- 154 :
}]
- 155 :
});
- 156 :
this.callParent(arguments);
- 157 :
this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
- 158 :
- 159 :
this.on('boxready', function() {
- 160 :
this.loadBubbles();
- 161 :
})
- 162 :
- 163 :
this.on('loadedCorpus', function(src, corpus) {
- 164 :
this.corpusLoaded = true;
- 165 :
if (this.bubbles) {
- 166 :
this.loadDocument();
- 167 :
} else {
- 168 :
this.loadBubbles();
- 169 :
}
- 170 :
}, this);
- 171 :
- 172 :
this.on("resize", function() {
- 173 :
if (this.bubbles) {
- 174 :
this.bubbles.size(this.body.getWidth(),this.body.getHeight());
- 175 :
}
- 176 :
});
- 177 :
- 178 :
this.on("documentselected", function(src, doc) {
- 179 :
this.setApiParam('docIndex', this.getCorpus().getDocument(doc).getIndex());
- 180 :
this.loadDocument();
- 181 :
})
- 182 :
},
- 183 :
- 184 :
setAudio: function(val) {
- 185 :
if (this.gainNode) {this.gainNode.gain.value=val ? 1 : 0;}
- 186 :
this.callParent(arguments)
- 187 :
},
- 188 :
- 189 :
handleCurrentTerm: function(term) {
- 190 :
if (this.oscillator) {this.oscillator.frequency.value = this.terms[term] ? parseInt((this.terms[term]-this.minFreq) * 2000 / (this.maxFreq-this.minFreq)) : 0;}
- 191 :
},
- 192 :
- 193 :
handleDocFinished: function() {
- 194 :
if (this.gainNode) {this.gainNode.gain.value = 0;}
- 195 :
var index = parseInt(this.getApiParam('docIndex'));
- 196 :
if (index+1<this.getCorpus().getDocumentsCount()) {
- 197 :
this.setApiParam('docIndex', index+1);
- 198 :
this.loadDocument();
- 199 :
}
- 200 :
},
- 201 :
- 202 :
loadDocument: function() {
- 203 :
var me = this, doc = this.getCorpus().getDocument(parseInt(this.getApiParam('docIndex')));
- 204 :
// if we're not in a tab panel, set the document title as part of the header
- 205 :
if (!this.up("tabpanel")) {
- 206 :
this.setTitle(this.localize('title') + " <span class='subtitle'>"+doc.getFullLabel()+"</span>");
- 207 :
}
- 208 :
- 209 :
doc.loadDocumentTerms(Ext.apply(this.getApiParams(["stopList"]), {
- 210 :
limit: 100
- 211 :
})).then(function(documentTerms) {
- 212 :
me.terms = {};
- 213 :
documentTerms.each(function(documentTerm) {
- 214 :
me.terms[documentTerm.getTerm()] = documentTerm.getRawFreq();
- 215 :
})
- 216 :
var values = Object.keys(me.terms).map(function(k){return me.terms[k]});
- 217 :
me.minFreq = Ext.Array.min(values);
- 218 :
me.maxFreq = Ext.Array.max(values);
- 219 :
me.getCorpus().loadTokens({whitelist: Object.keys(me.terms), noOthers: true, limit: 0, docIndex: me.getApiParam('docIndex')}).then(function(tokens) {
- 220 :
var words = [];
- 221 :
tokens.each(function(token) {
- 222 :
words.push(token.getTerm().toLowerCase());
- 223 :
})
- 224 :
me.bubbles.setLines([doc.getTitle(),words.join(" ")]);
- 225 :
me.bubbles.loop();
- 226 :
me.oscillator.frequency.value = 150;
- 227 :
me.gainNode.gain.value = me.getAudio() ? 1 : 0;
- 228 :
})
- 229 :
})
- 230 :
},
- 231 :
- 232 :
loadBubbles: function() {
- 233 :
if (this.bubbles === undefined && this.processingLoaded && this.bubblesAppCode !== undefined && this.getTargetEl() !== undefined) {
- 234 :
var canvas = this.getTargetEl().dom.querySelector('canvas');
- 235 :
this.bubbles = new Processing(canvas, this.bubblesAppCode);
- 236 :
this.bubbles.size(this.getTargetEl().getWidth(),this.getTargetEl().getHeight());
- 237 :
this.bubbles.frameRate(this.getApiParam('speed'));
- 238 :
this.bubbles.bindJavascript(this);
- 239 :
this.bubbles.noLoop();
- 240 :
- 241 :
this.bubblesAppCode = undefined;
- 242 :
- 243 :
if (this.corpusLoaded) {
- 244 :
this.loadDocument();
- 245 :
}
- 246 :
}
- 247 :
},
- 248 :
- 249 :
initComponent: function() {
- 250 :
// make sure to load script
- 251 :
Ext.Loader.loadScript({
- 252 :
url: this.getBaseUrl()+"resources/processingjs/processing.min.js",
- 253 :
onLoad: function() {
- 254 :
this.processingLoaded = true;
- 255 :
this.loadBubbles();
- 256 :
},
- 257 :
scope: this
- 258 :
});
- 259 :
- 260 :
Ext.Ajax.request({
- 261 :
url: this.getBaseUrl()+'resources/bubbles/bubbles.pjs',
- 262 :
success: function(data) {
- 263 :
this.bubblesAppCode = data.responseText;
- 264 :
this.loadBubbles();
- 265 :
},
- 266 :
scope: this
- 267 :
})
- 268 :
- 269 :
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
- 270 :
- 271 :
this.oscillator = audioCtx.createOscillator();
- 272 :
this.gainNode = audioCtx.createGain();
- 273 :
this.oscillator.connect(this.gainNode);
- 274 :
this.gainNode.connect(audioCtx.destination);
- 275 :
this.oscillator.frequency.value = 0;
- 276 :
this.oscillator.start();
- 277 :
this.gainNode.gain.value = 0;
- 278 :
- 279 :
this.callParent(arguments);
- 280 :
}
- 281 :
});