Skip to content

Instantly share code, notes, and snippets.

@leongaban
Last active August 29, 2015 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leongaban/2e1e1c5cf2cd76e9b3a6 to your computer and use it in GitHub Desktop.
Save leongaban/2e1e1c5cf2cd76e9b3a6 to your computer and use it in GitHub Desktop.
HighChartsDirective.js
/*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');
}
})();
@shaunakv1
Copy link

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:

function restoreChartSize() {
             if(!vs.chartObject.reflowreflowNow){
                vs.chartObject.reflowreflowNow = .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;
                }
             }

             vs.chartObject.reflowreflowNow();
        }

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.

@leongaban
Copy link
Author

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