Skip to content

Commit b962ee8

Browse files
Add support for highlighting dominant band frequencies in different colors if Short Time Frequency Transform values are provided by the server.
1 parent efa8b42 commit b962ee8

File tree

2 files changed

+272
-12
lines changed

2 files changed

+272
-12
lines changed

bundle.js

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,43 @@ require('./jquery.timer');
496496
var Highcharts = require('highcharts');
497497
var HighchartsAnnotations = require('highcharts-annotations')(Highcharts);
498498

499+
var frequencyBandVisualizationDefault = {
500+
bands: [
501+
{
502+
name: 'Delta',
503+
frequency: {
504+
min: 1,
505+
max: 4,
506+
},
507+
color: '#F77C00', // orange
508+
},
509+
{
510+
name: 'Mixed Freq.',
511+
frequency: {
512+
min: 4,
513+
max: 8,
514+
},
515+
color: '#25C700', // green
516+
},
517+
{
518+
name: 'Alpha',
519+
frequency: {
520+
min: 8,
521+
max: 13,
522+
},
523+
color: '#1968FF', // blue
524+
},
525+
{
526+
name: 'Beta',
527+
frequency: {
528+
min: 13,
529+
max: 30,
530+
},
531+
color: '#000000', // black
532+
},
533+
],
534+
};
535+
499536
$.widget('crowdcurio.TimeSeriesAnnotator', {
500537

501538
options: {
@@ -564,6 +601,15 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
564601
showReferenceLines: true,
565602
showTimeLabels: true,
566603
showChannelNames: true,
604+
frequencyBandVisualizationPerChannel: [
605+
frequencyBandVisualizationDefault,
606+
frequencyBandVisualizationDefault,
607+
frequencyBandVisualizationDefault,
608+
undefined,
609+
undefined,
610+
undefined,
611+
undefined,
612+
],
567613
features: {
568614
examplesModeEnabled: false,
569615
examples: [{
@@ -2245,11 +2291,16 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
22452291
gain: input.channel_gains[i],
22462292
values: input.channel_values[name]
22472293
}
2294+
if (input.channel_stft_values_absolute && input.channel_stft_values_absolute[i]) {
2295+
channel.stftValuesAbsolute = input.channel_stft_values_absolute[i];
2296+
}
22482297
channels.push(channel);
22492298
}
22502299
var output = {
22512300
channels: channels,
2252-
sampling_rate: input.sampling_rate
2301+
sampling_rate: input.sampling_rate,
2302+
stft_sample_frequencies: input.stft_sample_frequencies,
2303+
stft_segment_times: input.stft_segment_times,
22532304
}
22542305
return output;
22552306
},
@@ -2298,13 +2349,19 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
22982349
for(var ii=0; ii<data.channels.length; ii++) {
22992350
var channel = data.channels[ii];
23002351

2301-
that.vars.chart.series[ii].update({
2352+
var channelUpdate = {
23022353
pointStart: that.vars.currentWindowStart,
23032354
pointInterval: 1 / data.sampling_rate,
23042355
name: channel.name,
23052356
type: 'line',
23062357
data: channel.values
2307-
}, false); // false to supress redrawing after every series is added
2358+
};
2359+
var channelColor = that._getChannelColor(data, ii);
2360+
if (channelColor) {
2361+
channelUpdate.color = channelColor;
2362+
}
2363+
2364+
that.vars.chart.series[ii].update(channelUpdate, false); // false to supress redrawing after every series is added
23082365
}
23092366
that.vars.chart.xAxis[0].setExtremes(that.vars.currentWindowStart, that.vars.currentWindowStart + that.options.windowSizeInSeconds, false, false);
23102367
that.vars.chart.redraw(); // efficiently redraw the entire window in one go
@@ -2356,9 +2413,6 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
23562413
title: {
23572414
text: ''
23582415
},
2359-
tooltip: {
2360-
enabled: false
2361-
},
23622416
plotOptions: {
23632417
series: {
23642418
animation: false,
@@ -3060,11 +3114,87 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
30603114
type: 'line',
30613115
data: channels[ii].values
30623116
};
3117+
var channelColor = that._getChannelColor(data, ii);
3118+
if (channelColor) {
3119+
channel.color = channelColor;
3120+
channel.enableMouseTracking = true;
3121+
channel.stickyTracking = false;
3122+
channel.tooltip = that._getChannelTooltip(data, ii);
3123+
}
30633124
series.push(channel);
30643125
}
30653126
return(series);
30663127
},
30673128

3129+
_getChannelColor: function(data, channelIndex) {
3130+
var that = this;
3131+
var frequencyBandVisualization = that.options.frequencyBandVisualizationPerChannel[channelIndex];
3132+
if (!frequencyBandVisualization) {
3133+
return;
3134+
}
3135+
var bands = frequencyBandVisualization.bands;
3136+
var bandsFrequencyIndices = [];
3137+
bands.forEach(function(band) {
3138+
var frequencyIndexStart = 0;
3139+
var frequencyIndexEnd = data.stft_sample_frequencies.length - 1;
3140+
for (var f = 0; f < data.stft_sample_frequencies.length; ++f) {
3141+
if (data.stft_sample_frequencies[f] > band.frequency.min) {
3142+
frequencyIndexStart = f;
3143+
break;
3144+
}
3145+
}
3146+
for (var f = data.stft_sample_frequencies.length - 1; f >= 0; --f) {
3147+
if (data.stft_sample_frequencies[f] <= band.frequency.max) {
3148+
frequencyIndexEnd = f;
3149+
break;
3150+
}
3151+
}
3152+
bandsFrequencyIndices.push({
3153+
start: frequencyIndexStart,
3154+
end: frequencyIndexEnd,
3155+
});
3156+
});
3157+
var stftValuesAbsolute = data.channels[channelIndex].stftValuesAbsolute;
3158+
var stops = stftValuesAbsolute.map(function(frequencySamples, t) {
3159+
var frequencyPowersAggregates = bandsFrequencyIndices.map(function(bandFrequencyIndices) {
3160+
var frequencyPowers = frequencySamples.slice(bandFrequencyIndices.start, bandFrequencyIndices.end + 1)
3161+
var frequencyPowersSum = frequencyPowers.reduce(function(a, b) { return a + b; }, 0);
3162+
return frequencyPowersSum / frequencyPowers.length;
3163+
});
3164+
var dominantBandIndex = frequencyPowersAggregates.indexOf(Math.max.apply(Math, frequencyPowersAggregates));
3165+
var dominantBand = bands[dominantBandIndex];
3166+
var timeStepRelative = t / (stftValuesAbsolute.length - 1);
3167+
return [ timeStepRelative, dominantBand.color ];
3168+
});
3169+
var channelColor = {
3170+
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 0 },
3171+
stops: stops,
3172+
};
3173+
return channelColor;
3174+
},
3175+
3176+
_getChannelTooltip: function(data, channelIndex) {
3177+
var that = this;
3178+
var frequencyBandVisualization = that.options.frequencyBandVisualizationPerChannel[channelIndex];
3179+
if (!frequencyBandVisualization) {
3180+
return;
3181+
}
3182+
var bands = frequencyBandVisualization.bands;
3183+
var pointFormat = 'Rhythms:';
3184+
bands.forEach(function(band) {
3185+
pointFormat += '<span style="color: ' + band.color + '; font-weight: bold"> ' + band.name + ' (' + band.frequency.min + ' - ' + band.frequency.max + ' Hz)</span>';
3186+
});
3187+
var tooltip = {
3188+
enabled: true,
3189+
split: true,
3190+
useHTML: true,
3191+
headerFormat: '',
3192+
pointFormat: pointFormat,
3193+
footerFormat: '',
3194+
};
3195+
return tooltip;
3196+
},
3197+
30683198
_getFeatureColor: function(featureKey, isAnswer, confidence) {
30693199
var that = this;
30703200
var isAnswer = !!isAnswer;

lib/time-series-annotator.js

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,43 @@ require('./jquery.timer');
77
var Highcharts = require('highcharts');
88
var HighchartsAnnotations = require('highcharts-annotations')(Highcharts);
99

10+
var frequencyBandVisualizationDefault = {
11+
bands: [
12+
{
13+
name: 'Delta',
14+
frequency: {
15+
min: 1,
16+
max: 4,
17+
},
18+
color: '#F77C00', // orange
19+
},
20+
{
21+
name: 'Mixed Freq.',
22+
frequency: {
23+
min: 4,
24+
max: 8,
25+
},
26+
color: '#25C700', // green
27+
},
28+
{
29+
name: 'Alpha',
30+
frequency: {
31+
min: 8,
32+
max: 13,
33+
},
34+
color: '#1968FF', // blue
35+
},
36+
{
37+
name: 'Beta',
38+
frequency: {
39+
min: 13,
40+
max: 30,
41+
},
42+
color: '#000000', // black
43+
},
44+
],
45+
};
46+
1047
$.widget('crowdcurio.TimeSeriesAnnotator', {
1148

1249
options: {
@@ -75,6 +112,15 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
75112
showReferenceLines: true,
76113
showTimeLabels: true,
77114
showChannelNames: true,
115+
frequencyBandVisualizationPerChannel: [
116+
frequencyBandVisualizationDefault,
117+
frequencyBandVisualizationDefault,
118+
frequencyBandVisualizationDefault,
119+
undefined,
120+
undefined,
121+
undefined,
122+
undefined,
123+
],
78124
features: {
79125
examplesModeEnabled: false,
80126
examples: [{
@@ -1756,11 +1802,16 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
17561802
gain: input.channel_gains[i],
17571803
values: input.channel_values[name]
17581804
}
1805+
if (input.channel_stft_values_absolute && input.channel_stft_values_absolute[i]) {
1806+
channel.stftValuesAbsolute = input.channel_stft_values_absolute[i];
1807+
}
17591808
channels.push(channel);
17601809
}
17611810
var output = {
17621811
channels: channels,
1763-
sampling_rate: input.sampling_rate
1812+
sampling_rate: input.sampling_rate,
1813+
stft_sample_frequencies: input.stft_sample_frequencies,
1814+
stft_segment_times: input.stft_segment_times,
17641815
}
17651816
return output;
17661817
},
@@ -1809,13 +1860,19 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
18091860
for(var ii=0; ii<data.channels.length; ii++) {
18101861
var channel = data.channels[ii];
18111862

1812-
that.vars.chart.series[ii].update({
1863+
var channelUpdate = {
18131864
pointStart: that.vars.currentWindowStart,
18141865
pointInterval: 1 / data.sampling_rate,
18151866
name: channel.name,
18161867
type: 'line',
18171868
data: channel.values
1818-
}, false); // false to supress redrawing after every series is added
1869+
};
1870+
var channelColor = that._getChannelColor(data, ii);
1871+
if (channelColor) {
1872+
channelUpdate.color = channelColor;
1873+
}
1874+
1875+
that.vars.chart.series[ii].update(channelUpdate, false); // false to supress redrawing after every series is added
18191876
}
18201877
that.vars.chart.xAxis[0].setExtremes(that.vars.currentWindowStart, that.vars.currentWindowStart + that.options.windowSizeInSeconds, false, false);
18211878
that.vars.chart.redraw(); // efficiently redraw the entire window in one go
@@ -1867,9 +1924,6 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
18671924
title: {
18681925
text: ''
18691926
},
1870-
tooltip: {
1871-
enabled: false
1872-
},
18731927
plotOptions: {
18741928
series: {
18751929
animation: false,
@@ -2571,11 +2625,87 @@ $.widget('crowdcurio.TimeSeriesAnnotator', {
25712625
type: 'line',
25722626
data: channels[ii].values
25732627
};
2628+
var channelColor = that._getChannelColor(data, ii);
2629+
if (channelColor) {
2630+
channel.color = channelColor;
2631+
channel.enableMouseTracking = true;
2632+
channel.stickyTracking = false;
2633+
channel.tooltip = that._getChannelTooltip(data, ii);
2634+
}
25742635
series.push(channel);
25752636
}
25762637
return(series);
25772638
},
25782639

2640+
_getChannelColor: function(data, channelIndex) {
2641+
var that = this;
2642+
var frequencyBandVisualization = that.options.frequencyBandVisualizationPerChannel[channelIndex];
2643+
if (!frequencyBandVisualization) {
2644+
return;
2645+
}
2646+
var bands = frequencyBandVisualization.bands;
2647+
var bandsFrequencyIndices = [];
2648+
bands.forEach(function(band) {
2649+
var frequencyIndexStart = 0;
2650+
var frequencyIndexEnd = data.stft_sample_frequencies.length - 1;
2651+
for (var f = 0; f < data.stft_sample_frequencies.length; ++f) {
2652+
if (data.stft_sample_frequencies[f] > band.frequency.min) {
2653+
frequencyIndexStart = f;
2654+
break;
2655+
}
2656+
}
2657+
for (var f = data.stft_sample_frequencies.length - 1; f >= 0; --f) {
2658+
if (data.stft_sample_frequencies[f] <= band.frequency.max) {
2659+
frequencyIndexEnd = f;
2660+
break;
2661+
}
2662+
}
2663+
bandsFrequencyIndices.push({
2664+
start: frequencyIndexStart,
2665+
end: frequencyIndexEnd,
2666+
});
2667+
});
2668+
var stftValuesAbsolute = data.channels[channelIndex].stftValuesAbsolute;
2669+
var stops = stftValuesAbsolute.map(function(frequencySamples, t) {
2670+
var frequencyPowersAggregates = bandsFrequencyIndices.map(function(bandFrequencyIndices) {
2671+
var frequencyPowers = frequencySamples.slice(bandFrequencyIndices.start, bandFrequencyIndices.end + 1)
2672+
var frequencyPowersSum = frequencyPowers.reduce(function(a, b) { return a + b; }, 0);
2673+
return frequencyPowersSum / frequencyPowers.length;
2674+
});
2675+
var dominantBandIndex = frequencyPowersAggregates.indexOf(Math.max.apply(Math, frequencyPowersAggregates));
2676+
var dominantBand = bands[dominantBandIndex];
2677+
var timeStepRelative = t / (stftValuesAbsolute.length - 1);
2678+
return [ timeStepRelative, dominantBand.color ];
2679+
});
2680+
var channelColor = {
2681+
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 0 },
2682+
stops: stops,
2683+
};
2684+
return channelColor;
2685+
},
2686+
2687+
_getChannelTooltip: function(data, channelIndex) {
2688+
var that = this;
2689+
var frequencyBandVisualization = that.options.frequencyBandVisualizationPerChannel[channelIndex];
2690+
if (!frequencyBandVisualization) {
2691+
return;
2692+
}
2693+
var bands = frequencyBandVisualization.bands;
2694+
var pointFormat = 'Rhythms:';
2695+
bands.forEach(function(band) {
2696+
pointFormat += '<span style="color: ' + band.color + '; font-weight: bold"> ' + band.name + ' (' + band.frequency.min + ' - ' + band.frequency.max + ' Hz)</span>';
2697+
});
2698+
var tooltip = {
2699+
enabled: true,
2700+
split: true,
2701+
useHTML: true,
2702+
headerFormat: '',
2703+
pointFormat: pointFormat,
2704+
footerFormat: '',
2705+
};
2706+
return tooltip;
2707+
},
2708+
25792709
_getFeatureColor: function(featureKey, isAnswer, confidence) {
25802710
var that = this;
25812711
var isAnswer = !!isAnswer;

0 commit comments

Comments
 (0)