Last active
August 29, 2015 14:26
-
-
Save leongaban/2e1e1c5cf2cd76e9b3a6 to your computer and use it in GitHub Desktop.
HighChartsDirective.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global angular*/ | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* @name highChart | |
* @namespace Directive | |
* @desc Displays our custom HighCharts chart | |
*/ | |
(function() { "use strict"; | |
angular | |
.module('highChartDirective', []).controller('HighChartCtrl', HighChartCtrl) | |
.directive('highChart', directive); | |
function directive () { | |
var directive = { | |
templateUrl : "panels/charting/highChart.html", | |
replace: true, | |
bindToController: true, | |
controller: HighChartCtrl, | |
link: link, | |
scope: false | |
}; | |
return directive; | |
function link(scope, element, attrs) {} | |
} | |
HighChartCtrl.$inject = [ | |
'$scope', | |
'$rootScope', | |
'$window', | |
'$timeout', | |
'ScopeFactory', | |
'ApiFactory', | |
'TagFactory', | |
'TimeSpanFactory', | |
'BuildSocialMediaFactory']; | |
function HighChartCtrl( | |
$scope, | |
$rootScope, | |
$window, | |
$timeout, | |
ScopeFactory, | |
ApiFactory, | |
TagFactory, | |
TimeSpanFactory, | |
BuildSocialMediaFactory) { | |
/** Init highChart scope */ | |
/** ----------------------------------------------------------------- */ | |
var vs = $scope, | |
root = ScopeFactory.getScope('root'), | |
chart, | |
notifications = {}, | |
control = {}, | |
controlHeader = {}, | |
viewHeader = {}, | |
symbolUrls = {}, | |
symbolTerms = {}, | |
timestamp = 0, | |
lookupSymbols = ["^NIOALL", "^NIOAFN", "^NIOAMD"], | |
lookupColors = ["#FDE18D", "#7DD0FA", "#58A6EC"]; | |
vs.highChartMax = false, | |
vs.ticker = '', | |
vs.chartObject = {}, | |
vs.interval = 'day'; | |
ScopeFactory.saveScope('highChart', vs); | |
/** Hoisted functions */ | |
/** ----------------------------------------------------------------- */ | |
vs.displayChart = displayChart, | |
vs.getTickerTags = getTickerTags, | |
vs.addTagHistorgram = addTagHistorgram, | |
vs.restoreChartSize = restoreChartSize, | |
vs.changePeriodicity = changePeriodicity, | |
vs.changeRange = changePeriodicity, | |
vs.shareChartExample = shareChartExample, | |
vs.shareChartStockTwits = shareChartStockTwits, | |
vs.quoteFeedAttached = 0; | |
Highcharts.setOptions({ | |
global: { | |
timezoneOffset: 5 * 60 | |
} | |
}); | |
var intervalDisplayMap = { | |
"day" : "1 D", | |
"week" : "1 W", | |
"month" : "1 Mo", | |
"1" : "1 Min", | |
"5" : "5 Min", | |
"30" : "30 Min" | |
}; | |
function restoreChartSize() { | |
console.log('restoreChartSize'); | |
if (!vs.chartObject.reflowNow) { | |
vs.chartObject.reflowNow = vs.chartObject.reflowNow = function() { | |
this.containerHeight = this.options.chart.height || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'height'); | |
this.containerWidth = this.options.chart.width || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'width'); | |
this.setSize(this.containerWidth, this.containerHeight, true); | |
this.hasUserSize = null; | |
} | |
} | |
vs.chartObject.reflowNow(); | |
} | |
function changePeriodicity(newInterval) { | |
vs.interval = newInterval; | |
var rightDate = new Date(); | |
var leftDate = new Date(); | |
if ( newInterval == "hour" || newInterval == 'lh' ) { | |
leftDate.setHours(rightDate.getHours()-1); | |
} | |
else if ( newInterval == "day" || newInterval == 'ld' ) { | |
leftDate.setDate(rightDate.getDate()-1); | |
} | |
else if ( newInterval == "week" || newInterval == 'lw' ) { | |
leftDate.setDate(rightDate.getDate()-7); | |
} | |
else if ( newInterval == "month" || newInterval == '1m' ) { | |
leftDate.setDate(rightDate.getDate()-30); | |
} | |
else if ( newInterval == "3month" || newInterval == '3m' ) { | |
leftDate.setDate(rightDate.getDate()-90); | |
} | |
else if ( newInterval == "year" || newInterval == '1y' ) { | |
leftDate.setDate(rightDate.getDate()-365); | |
} | |
else if ( newInterval == "2year" || newInterval == '2y' || newInterval == 'max' ) { | |
leftDate.setDate(rightDate.getDate()-730); | |
} | |
vs.chartObject.xAxis[0].setExtremes(leftDate.getTime(), rightDate.getTime()); | |
} | |
function getTickerTags() { | |
// Grab tickers and tags: | |
var tickerTags = TagFactory.retrieveTickerTags('all'); | |
var tickers = _(tickerTags).pluck('ticker').value(); | |
var tagObjs = tickerTags.map(function(el) { return el.tags; }); | |
var tags = _.flatten(tagObjs); | |
// console.log(tags); | |
// Ticker & Tags object arrays: | |
addTagHistorgram(tickers, tags); | |
} | |
function addTagHistorgram(tickers, tags) { | |
var delay = 0; | |
var last = 0; | |
for (var i = 0; i < lookupSymbols.length; i++) { | |
var url = ''; | |
var process_request = 0; | |
if (i < tags.length) { | |
url = '/app/api/social/twitter/volume/' + tags[i].term_id; | |
} | |
if (url && !(lookupSymbols[i] in symbolUrls)) { | |
// console.log('adding new series'); | |
process_request = 1; | |
} | |
if (url && lookupSymbols[i] in symbolUrls && symbolUrls[lookupSymbols[i]] !== url) { | |
// console.log('removing series', tags[i].term_id); | |
for(var j=0;j<vs.chartObject.series.length;j++){ | |
if (symbolTerms[lookupSymbols[i]] === vs.chartObject.series[j].name) { | |
vs.chartObject.series[j].remove(); | |
} | |
} | |
process_request = 1; | |
} | |
if (!url && lookupSymbols[i] in symbolUrls) { | |
// console.log('removing dead series'); | |
for(var j=0;j<vs.chartObject.series.length;j++){ | |
if (symbolTerms[lookupSymbols[i]] === vs.chartObject.series[j].name) { | |
vs.chartObject.series[j].remove(); | |
} | |
} | |
delete symbolUrls[lookupSymbols[i]]; | |
delete symbolTerms[lookupSymbols[i]]; | |
} | |
if (process_request && url) { | |
// console.log('processing series', tags[i].term_id); | |
if (i == tags.length - 1) { | |
last = 1; | |
} | |
symbolUrls[lookupSymbols[i]] = url; | |
symbolTerms[lookupSymbols[i]] = tags[i].term; | |
stxComparison(url, tags[i].term, i, delay, last); | |
delay += 250; | |
} | |
} | |
if (last == 0) { | |
setTimeout(function() { | |
changePeriodicity(vs.interval); | |
}, 1000); | |
} | |
} | |
function stxComparison(url, term, i, delay, last) { | |
setTimeout(function() { | |
var e = vs.chartObject.xAxis[0].getExtremes(); | |
var final_url = buildFullUrl(url, Math.round(e.min/1000), Math.round(e.max/1000)); | |
ApiFactory.quotes(final_url).then(function(data) { | |
var quote_data = formatQuotes(data, 'tweets'); | |
vs.chartObject.addSeries({ | |
showInLegend: true, | |
yAxis: 1, | |
type: 'area', | |
name: term, | |
color: lookupColors[i], | |
data: quote_data, | |
dataGrouping: { | |
enabled: false | |
} | |
}); | |
if (last) { | |
changePeriodicity(vs.interval); | |
} | |
}); | |
}, delay); | |
} | |
function buildFullUrl(url, start_epoch, end_epoch) { | |
var group = 7 * 24 * 60 * 60; | |
if (start_epoch) { | |
if ((end_epoch - start_epoch) <= 12 * 60 * 60) { | |
group = 60; | |
} | |
else if ((end_epoch - start_epoch) <= 3 * 24 * 60 * 60) { | |
group = 15 * 60; | |
} | |
else if ((end_epoch - start_epoch) <= 2 * 7 * 24 * 60 * 60) { | |
group = 60 * 60; | |
} | |
else if ((end_epoch - start_epoch) <= 6 * 7 * 24 * 60 * 60) { | |
group = 6 * 60 * 60; | |
} | |
else if ((end_epoch - start_epoch) <= 18 * 7 * 24 * 60 * 60) { | |
group = 24 * 60 * 60; | |
} | |
// console.log(group); | |
return url + '?start=' + start_epoch + '&end=' + end_epoch + '&group=' + group; | |
} else { | |
return url + '?limit=8760&group=' + group; | |
} | |
} | |
function formatQuotes(data, type) { | |
var data_point = 'price'; | |
var data_location = 'quotes'; | |
if (type === 'tweets') { | |
data_point = 'tweets'; | |
data_location = 'frequency_counts'; | |
} | |
var quote_data = []; | |
var last_date = ''; | |
data.data[data_location].reverse(); | |
for (var j=0; j<data.data[data_location].length; j++) { | |
last_date = data.data[data_location][j]['start_epoch'] * 1000; | |
quote_data.push([data.data[data_location][j]['start_epoch'] * 1000, data.data[data_location][j][data_point]]); | |
} | |
quote_data.push([last_date,null]); | |
return quote_data; | |
} | |
function afterClick(e) { | |
timestamp = Math.round(e.point.category/1000); | |
if (timestamp !== vs.timestamp) { | |
vs.timestamp = timestamp; | |
BuildSocialMediaFactory.buildSocialStreams(vs.ticker, null, vs.timestamp); | |
} | |
} | |
/** | |
* Load new data depending on the selected min and max | |
*/ | |
function afterSetExtremes(e) { | |
vs.chartObject.showLoading('Loading data from server...'); | |
var counter = 0; | |
timestamp = Math.round(e.max/1000); | |
if (vs.timestamp === undefined) { | |
vs.timestamp = timestamp; | |
} | |
console.log(vs.timestamp, timestamp, Math.abs(timestamp - vs.timestamp)); | |
if (Math.abs(timestamp - vs.timestamp) > 360) { | |
vs.timestamp = timestamp; | |
BuildSocialMediaFactory.buildSocialStreams(vs.ticker, null, vs.timestamp); | |
} | |
for (var series=0; series<vs.chartObject.series.length; series++) { | |
if (vs.chartObject.series[series].name === "Navigator") { | |
continue; | |
} | |
counter += 1; | |
var symbol = vs.ticker; | |
var data_type = 'quotes'; | |
if (counter > 1) { | |
var data_type = 'tweets'; | |
symbol = lookupSymbols[counter-2]; | |
} | |
var url = buildFullUrl(symbolUrls[symbol], Math.round(e.min/1000), Math.round(e.max/1000)); | |
// console.log('Getting series', series, vs.chartObject.series[series].name, symbolUrls[symbol]); | |
getQuotes(url, series, data_type); | |
} | |
} | |
function getQuotes(url, series, data_type) { | |
ApiFactory.quotes(url).then(function(data) { | |
// console.log(' getting series', series); | |
var quote_data = formatQuotes(data, data_type); | |
if (vs.chartObject.series) { | |
vs.chartObject.series[series].setData(quote_data); | |
} | |
vs.chartObject.hideLoading(); | |
}); | |
} | |
Number.prototype.formatCommas = function() { | |
var x = this; | |
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |
} | |
Number.prototype.formatMoney = function(c, d, t) { | |
var n = this, | |
c = isNaN(c = Math.abs(c)) ? 2 : c, | |
d = d == undefined ? "." : d, | |
t = t == undefined ? "," : t, | |
s = n < 0 ? "-" : "", | |
i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", | |
j = (j = i.length) > 3 ? j % 3 : 0; | |
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); | |
}; | |
function tooltipFormatter(that) { | |
//console.log(this); | |
if (this.color === "#4C73FF" && this.y > 0) { | |
this.y = '$' + this.y.formatMoney(2); | |
} else if (this.y > 999) { | |
this.y = this.y.formatCommas(); | |
} | |
return '<span style="color:' + this.color + '">\u25CF</span> ' + this.series.name + ': <b>' + this.y + '</b><br/>'; | |
} | |
function volumeFormatter() { | |
if (this.value > 1000000) { | |
this.value = Math.round(this.value/1000000) + 'M'; | |
} else if (this.value > 1000) { | |
this.value = Math.round(this.value/1000) + 'K'; | |
} | |
return this.value; | |
} | |
/* | |
* Initial function that is called when chart loads. | |
* If you want the chart to default to a security, | |
* or if you're passing a security in with a query string then load it here | |
*/ | |
function displayChart(ticker) { | |
if (ticker === undefined) { | |
ticker = 'SPY'; | |
} | |
vs.ticker = ticker; | |
symbolUrls = {}; | |
//if (vs.chartObject) { | |
// vs.chartObject.destroy(); | |
//} | |
function broadcastChartloaded() { | |
$rootScope.$broadcast('highChartLoaded'); | |
} | |
var url = '/app/api/tickers/quotes/'+ticker; | |
symbolUrls[ticker] = url; | |
ApiFactory.quotes(buildFullUrl(url)).then(function (data) { | |
var quote_data = formatQuotes(data, 'quotes'); | |
var highChartContainerWidth = document.getElementById('highchart-container').clientWidth; | |
var highChartContainerHeight = document.getElementById('highchart-container').clientHeight; | |
// Create the chart | |
vs.chartConfig = { | |
options: { | |
credits: { enabled: false }, | |
legend: { | |
itemStyle: { | |
color: "#333333", | |
cursor: "pointer", | |
fontSize: "10px", | |
fontWeight: "normal" | |
}, | |
enabled: true, | |
floating: true, | |
align: 'left', | |
verticalAlign: 'top', | |
x: 60 | |
}, | |
chart : { | |
zoomType: 'x', | |
events: { | |
load: function () { | |
// HighChart loaded callback: | |
broadcastChartloaded(); | |
} | |
} | |
}, | |
scrollbar: { | |
enabled: false, | |
liveRedraw: false | |
}, | |
navigator : { | |
enabled:true, | |
adaptToUpdatedData: false, | |
series : { | |
data : quote_data | |
} | |
}, | |
rangeSelector: { | |
enabled: false, | |
}, | |
tooltip: { | |
pointFormatter: tooltipFormatter, | |
shared: true | |
}, | |
exporting: { enabled: false }, | |
plotOptions: { | |
series: { | |
point: { | |
events: { | |
click: afterClick, | |
} | |
} | |
}, | |
area: { | |
stacking: 'normal', | |
} | |
} | |
}, | |
size: { | |
width: highChartContainerWidth, | |
height: highChartContainerHeight | |
}, | |
exporting: { enabled: false }, | |
useHighStocks: true, | |
xAxis : { | |
dateTimeLabelFormats : { | |
hour: '%I %p', | |
minute: '%I:%M %p' | |
}, | |
events : { | |
afterSetExtremes : afterSetExtremes | |
}, | |
minRange: 3600 * 1000 // one hour | |
}, | |
yAxis: [{ // Primary yAxis | |
labels: { | |
format: '${value:.2f}', | |
style: { | |
color: '#4C73FF', | |
} | |
}, | |
title: { | |
text: 'Price', | |
style: { | |
color: '#4C73FF', | |
} | |
} | |
}, | |
{ // Secondary yAxis | |
gridLineWidth: 0, | |
title: { | |
text: 'Tweets', | |
style: { | |
color: '#FDE18D' | |
// Pick one - "#FDE18D", "#7DD0FA", "#58A6EC" | |
} | |
}, | |
labels: { | |
formatter: volumeFormatter, | |
style: { | |
color: '#FDE18D' | |
} | |
}, | |
opposite: false | |
}], | |
func: function(chart) { | |
vs.chartObject = chart; | |
$timeout(function() { | |
console.log('chart.reflow();'); | |
vs.chartObject.reflow(); | |
}, 0); | |
}, | |
series : [{ | |
zIndex: 1000, | |
color: '#4C73FF', | |
data: quote_data, | |
name: ticker, | |
dataGrouping: { | |
enabled: false | |
} | |
}] | |
}; | |
getTickerTags(); | |
}); | |
} | |
// chart = this.chartConfig.getHighcharts(); | |
// chart.reflowNow = function() { | |
// this.containerHeight = this.options.chart.height || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'height'); | |
// this.containerWidth = this.options.chart.width || window.window.HighchartsAdapter.adapterRun(this.renderTo, 'width'); | |
// this.setSize(this.containerWidth, this.containerHeight, false); | |
// this.hasUserSize = null; | |
// } | |
function shareChartStockTwits(body) { | |
var data = save_chart(vs.chartObject); | |
var share_id = guid(); | |
var url = "/app/api/social/share/chart/"; | |
// retrieve tickerTag objects: | |
var tickerTags = TagFactory.retrieveTickerTags('all'); | |
// grab tickers: | |
var tickers = _(tickerTags).pluck('ticker').value(); | |
// grab tags: | |
var tagObjs = tickerTags.map(function(el) { return el.tags; }); | |
var tags = _.flatten(tagObjs); | |
var payload = { | |
"id": share_id, | |
"image": data, | |
"tags": tags, | |
"ticker": vs.ticker, | |
"sentiment" : 'neutral', | |
"body": body | |
}; | |
notifications = ScopeFactory.getScope('notifications'); | |
ApiFactory.shareChart(payload).success(function(data) { | |
viewHeader.vh.chartShareModal = false; | |
notifications.setupNotification('Chart shared!', 'success', true); | |
}) | |
.error(function(data, status) { | |
// console.error('Repos error', status, data); | |
viewHeader.vh.chartShareModal = false; | |
notifications.setupNotification('Something went wrong... Check your Stock Twits account and try again.', 'failure', true); | |
}); | |
} | |
var EXPORT_WIDTH = 1000; | |
function save_chart(chart) { | |
var render_width = EXPORT_WIDTH; | |
var render_height = render_width * chart.chartHeight / chart.chartWidth | |
// Get the cart's SVG code | |
var svg = vs.chartObject.container.innerHTML; | |
svg = vs.chartObject.sanitizeSVG(svg); | |
// Create a canvas | |
var canvas = document.createElement('canvas'); | |
canvas.height = render_height; | |
canvas.width = render_width; | |
document.body.appendChild(canvas); | |
// Create an image and draw the SVG onto the canvas | |
canvg(canvas, svg, { | |
scaleWidth: render_width, | |
scaleHeight: render_height, | |
ignoreDimensions: true | |
}); | |
var data = canvas.toDataURL("image/png") | |
return data; | |
} | |
function download(data, filename) { | |
var a = document.createElement('a'); | |
a.download = filename; | |
a.href = data | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
function guid() { | |
function s4() { | |
return Math.floor((1 + Math.random()) * 0x10000) | |
.toString(16) | |
.substring(1); | |
} | |
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | |
s4() + '-' + s4() + s4() + s4(); | |
} | |
function shareChartExample() { | |
var filename = 'TickerTags-' + guid() + '.png'; | |
var data = save_chart(vs.chartObject); | |
download(data, filename); | |
} | |
// Load entire scope then tell Root this is ready: | |
root.initer('highChartDirective'); | |
} | |
})(); |
Getting reflow
is undefined here vs.chartObject.reflowreflowNow = reflowNow = function() {
I had to remove the .
before reflowNow
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Referring to stackoverflow discussion :
You don't need to pull out the chart object. The original author of this code seems to already be pulling object and saving it in
vs.chartObject
I would just modify the blank
toggleChartSize()
method you have to look like and rename it:And do any logic for toggling in there. The function will be added to the chart object once then never again.
Under /** Hoisted functions */ don't for get to register the new mthod like so
vs.restoreChartSize = restoreChartSize
then just use it as any other scope method from angular view.