<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ZingSoft Demo</title>
<script nonce="undefined" src="https://cdn.zingchart.com/zingchart.min.js"></script>
<style>
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
#myChart {
height: 100%;
width: 100%;
min-height: 700px;
position: relative;
z-index: 500;
}
#crosshair-image {
position: absolute;
z-index: 1000;
left: 908px;
height: 50px;
width: 50px;
background-image: transparent;
background-repeat: no-repeat;
}
/* split up the images into 4 moon phases 25px 25px pngs. You can crop your own */
.phase_1 {
background-image: url("//demos.zingchart.com/view/67J40ZDF/moon_phase_1.png");
}
.phase_2 {
background-image: url("//demos.zingchart.com/view/67J40ZDF/moon_phase_2.png");
}
.phase_3 {
background-image: url("//demos.zingchart.com/view/67J40ZDF/moon_phase_3.png");
}
.phase_4 {
background-image: url("//demos.zingchart.com/view/67J40ZDF/moon_phase_4.png");
}
</style>
</head>
<body>
<div id="myChart">
<div id="crosshair-image"></div>
</div>
<script>
ZC.LICENSE = ["569d52cefae586f634c54f86dc99e6a9", "b55b025e438fa8a98e32482b5f768ff5"]; /* Globals */
var MILLISECONDS_IN_A_DAY = 86400000;
var MILLISECONDS_IN_A_HOUR = 3600000;
var MILLISECONDS_IN_A_MINUTE = 60000;
var MILLISECONDS_IN_A_SECOND = 1000;
var START_OF_2016_TIMESTAMP = 1451635200000;
//var START_OF_2017_TIMESTAMP = 1451635200;
var currentYearTimestamp = START_OF_2016_TIMESTAMP;
var currentStringYear = '2016';
var DEGREES_SYMBOL = '°';
var MONTH_LABELS = ['Jan 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Feb 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Mar 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Apr 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'May 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'June 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'July 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Aug 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Sep 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Oct 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Nov 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Dec 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']; //length is 366
var SCALE_MARKER_COLOR = '#bdbdbd';
var SCALE_MARKER_LINE_WIDTH = 1;
var SCALE_MARKER_ALPHA = .7;
var SCALE_MARKERS = [ // scale markers used for more precise vertical guidelines
{
type: 'line',
range: [31],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [60],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [91],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [121],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [152],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [182],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [213],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [244],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [274],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [305],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [335],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
}
];
var myConfig = {
layout: '3x1',
graphset: [{ // start sunrise/sunset graph
id: 'sunrise_sunset_chart',
type: 'line',
utc: true,
plot: {
maxTrackers: 1000, // max amount of nodes to be drawn. Also applies event listeners to 1000 nodes so faster if this is off
highlightState: { // legend hover line state
lineWidth: 5
},
marker: { // turn nodes off so its just the line
visible: false
}
},
plotarea: {
margin: '10 80 60 80'
},
legend: {
highlightPlot: true, // hover highlight
marginBottom: 0,
marginRight: 80,
layout: 'h'
},
scaleX: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
labels: MONTH_LABELS,
maxItems: 366, // dont allow more than this amount of items
itemsOverlap: true, // force items to not disappear
step: 'day',
guide: { //hide the guide lines
visible: false
},
tick: { // hide ticks
visible: false
},
transform: {
type: 'date',
all: '%M %d'
},
label: {
text: 'Days'
},
markers: SCALE_MARKERS // these are your new guidelines
},
scaleY: {
minValue: 19800000, // 05:30:00 am
maxValue: 26100000, // 07:15:00 am
step: (MILLISECONDS_IN_A_MINUTE * 15),
maxItems: 8, // force 8 items
itemsOverlap: true, // helps force 8 items
lineColor: '#ff5722',
tick: {
lineColor: '#ff5722'
},
guide: {
visible: false
},
label: {
text: 'Sunrise Time (AM)'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
scaleY2: {
minValue: 63000000, // 05:30:00 pm
maxValue: 69300000, // 07:15:00 pm
step: (MILLISECONDS_IN_A_MINUTE * 15),
maxItems: 8, // force 8 items
itemsOverlap: true, // helps force 8 items
lineColor: '#ff9800',
tick: {
lineColor: '#ff9800'
},
guide: {
visible: false
},
label: {
text: 'Sunset Time (PM)'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
tooltip: {
visible: false
}, // turn off tooltip
crosshairX: {
shared: true,
plotLabel: { // crosshair tooltip
headerText: '%kv ' + currentStringYear,
text: '<span style="color:%color">%t:</span> %vv',
fontSize: 15,
padding: 10,
borderRadius: 5
},
scaleLabel: {
visible: false
}, // highlight x-axis off
marker: { // highlight marker
type: 'circle',
size: 4
}
},
series: []
},
{
id: 'azimuth_chart',
type: 'line',
utc: true,
plot: {
maxTrackers: 1000,
highlightState: {
lineWidth: 5
},
marker: {
visible: false
}
},
plotarea: {
margin: '10 80 60 80'
},
legend: {
highlightPlot: true,
marginBottom: 0,
marginRight: 80,
layout: 'h'
},
scaleX: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
labels: MONTH_LABELS,
maxItems: 366,
itemsOverlap: true,
step: 'day',
guide: {
visible: false
},
tick: {
visible: false
},
transform: {
type: 'date',
all: '%M %d'
},
label: {
text: 'Days'
},
markers: SCALE_MARKERS
},
scaleY: {
values: '60:120:10',
maxItems: 7,
itemsOverlap: true,
format: '%v' + DEGREES_SYMBOL,
guide: {
visible: false
},
lineColor: '#ff5722',
tick: {
lineColor: '#ff5722'
},
label: {
text: 'Sunrise Direction'
}
},
scaleY2: {
values: '240:300:10',
maxItems: 7,
itemsOverlap: true,
format: '%v' + DEGREES_SYMBOL,
guide: {
visible: false
},
lineColor: '#ff9800',
tick: {
lineColor: '#ff9800'
},
label: {
text: 'Sunset Direction'
}
},
tooltip: {
visible: false
},
crosshairX: {
shared: true,
plotLabel: {
headerText: '%kv ' + currentStringYear,
text: '<span style="color:%color">%t:</span> %v' + DEGREES_SYMBOL,
fontSize: 15,
padding: 10,
borderRadius: 5
},
scaleLabel: {
visible: false
},
marker: {
type: 'circle',
size: 4
}
},
series: []
}, // end azimuth graph
{ // start sunrise/sunset graph
id: 'moonrise_moonset_chart',
type: 'line',
utc: true,
plot: {
maxTrackers: 1000,
highlightState: {
lineWidth: 5
},
marker: {
visible: false
}
},
plotarea: {
margin: '10 80 60 80'
},
legend: {
highlightPlot: true,
marginBottom: 0,
marginRight: 80,
layout: 'h'
},
scaleX: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
labels: MONTH_LABELS,
maxItems: 366,
itemsOverlap: true,
step: 'day',
guide: {
visible: false
},
tick: {
visible: false
},
transform: {
type: 'date',
all: '%M %d'
},
label: {
text: 'Days'
},
markers: SCALE_MARKERS
},
scaleX2: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
maxItems: 400,
itemsOverlap: true,
step: 'day',
guide: {
visible: false
},
tick: {
visible: false
},
},
scaleY: {
minValue: 0,
maxValue: MILLISECONDS_IN_A_DAY,
step: MILLISECONDS_IN_A_HOUR * 2,
maxItems: 7,
itemsOverlap: true,
guide: {
visible: false
},
label: {
text: 'Moonrise Time'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
scaleY2: {
minValue: 0,
maxValue: MILLISECONDS_IN_A_DAY,
step: MILLISECONDS_IN_A_HOUR * 2,
maxItems: 7,
itemsOverlap: true,
guide: {
visible: false
},
label: {
text: 'Moonset Time'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
tooltip: {
visible: false
},
crosshairX: {
shared: true,
plotLabel: {
headerText: '%kv ' + currentStringYear,
text: '<span style="color:%color">%t:</span> %vv',
fontSize: 15,
padding: 10,
borderRadius: 5,
},
scaleLabel: {
visible: false
},
marker: {
type: 'circle',
size: 4
}
},
series: []
}
]
};
zingchart.render({
id: 'myChart',
data: myConfig,
height: '100%',
width: '100%'
});
/*
*
* Example time: "113.63°"
*/
function parseDegrees(sDegrees) {
return Number(sDegrees.slice(0, sDegrees.length - 1));
}
/*
*
* Example time: "1, 5:50:56 AM"
* returns milliseconds value
*/
function parseStringToTimestamp(sTime) {
if (sTime.length <= 1) {
return null; // null value if blank cell
}
/*
* Y axis are plotted from 0 - 24hrs in milliseconds.
* Since x-axis already plots day we don't need the exact timestamp
* for this day of the year. Just the offset in milliseconds.
*
*/
var aTime = sTime.split(/\s/); // separate am/pm
parsedTime = aTime[0]; // asign time
parsedTime = parsedTime.split(':'); // split time
if (aTime[1].toLowerCase() == 'pm') {
if (Number(parsedTime[0]) !== 12)
parsedTime[0] = Number(parsedTime[0]) + 12;
} else { // if am
if (Number(parsedTime[0]) === 12)
parsedTime[0] = 0; // convert 12:00AM to Unix time
}
parsedTime[0] = Number(parsedTime[0]) * MILLISECONDS_IN_A_HOUR;
parsedTime[1] = Number(parsedTime[1]) * MILLISECONDS_IN_A_MINUTE;
parsedTime[2] = Number(parsedTime[2]) * MILLISECONDS_IN_A_SECOND;
return parsedTime[0] + parsedTime[1] + parsedTime[2];
}
function parseDataAndRenderChart() {
var arrayOfRows = this.responseText.split('\n');
var azimuthSeries = [];
var sunriseSunsetSeries = [];
var moonriseMoonsetSeries = [];
// turn rows as a string into an array
arrayOfRows = arrayOfRows.map(function(obj) {
return obj.split(',');
});
// initialize azimuth chart series
azimuthSeries[0] = {
text: 'Sunrise Direction',
values: [],
scales: 'scale-x, scale-y',
lineColor: '#ff5722'
};
azimuthSeries[1] = {
text: 'Sunset Direction',
values: [],
scales: 'scale-x, scale-y-2',
lineColor: '#ff9800'
};
// initialize sunrise/sunset chart series
sunriseSunsetSeries[0] = {
text: 'Sunrise',
values: [],
scales: 'scale-x, scale-y',
lineColor: '#ff5722'
};
sunriseSunsetSeries[1] = {
text: 'Sunset',
values: [],
scales: 'scale-x, scale-y-2',
lineColor: '#ff9800'
};
// initialize moonrise/moonset chart series
moonriseMoonsetSeries[0] = {
text: 'Moonrise',
values: [],
scales: 'scale-x, scale-y',
lineColor: '#9e9e9e'
};
moonriseMoonsetSeries[1] = {
text: 'Moonset',
values: [],
scales: 'scale-x, scale-y-2',
lineColor: '#212121'
};
/*
* ignore arrayOfRows[0] those are the titles
* assign millisecond timestamp value and string value to float
* [[]] array of arrays
*/
for (var i = 1; i < arrayOfRows.length; i++) {
// compile azimuth data
azimuthSeries[0].values.push( // push to series 0
parseDegrees(arrayOfRows[i][2])
);
azimuthSeries[1].values.push( // push to series 1
parseDegrees(arrayOfRows[i][4])
);
// compile sunrise/sunset data
sunriseSunsetSeries[0].values.push( // push to series 0
parseStringToTimestamp(arrayOfRows[i][1])
);
sunriseSunsetSeries[1].values.push( // push to series 1
parseStringToTimestamp(arrayOfRows[i][3])
);
// compile moonrise/moonset data
moonriseMoonsetSeries[0].values.push( // push to series 0
parseStringToTimestamp(arrayOfRows[i][5])
);
moonriseMoonsetSeries[1].values.push( // push to series 1
parseStringToTimestamp(arrayOfRows[i][6])
);
}
// load data into chart
zingchart.exec('myChart', 'setseriesdata', {
graphid: 'azimuth_chart',
update: false,
data: azimuthSeries
});
zingchart.exec('myChart', 'setseriesdata', {
graphid: 'sunrise_sunset_chart',
update: false,
data: sunriseSunsetSeries
});
zingchart.exec('myChart', 'appendseriesdata', {
graphid: 'moonrise_moonset_chart',
update: false,
data: moonriseMoonsetSeries
});
zingchart.exec('myChart', 'update');
}
// request data from url
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", parseDataAndRenderChart);
oReq.open("GET", 'https://app.zingsoft.com/api/file/GSNQC6UG/xqIP1B9ZRXiOmCCmLfCU_sun_moon_rise_azimuth.csv'); // exported google sheets to csv (thats it)
oReq.send();
/*
* Event listener for crosshair and how to change image.
* This will change the class (check CSS tab) on the div (positioned absolute over the chart)
* it will update the image based on the day (nodeindex).
*
* eg. nodeindex 32 is february 1st
*/
var imageContainer = null;
function bindImageOnMove(e) {
// event is fired for all 3 graphs. For speed only do this once.
if (e.graphid === "moonrise_moonset_chart") {
// easy way to cache the reference to the element
imageContainer = (imageContainer != null) ? imageContainer : document.getElementById('crosshair-image');
// used CSS modifications over chart modifcations for faster speed.
imageContainer.style.left = e.guide.x + 'px';
// if node index exists for at least one plot
if (e.items[0] && e.items[0].nodeindex) {
switch (e.items[0].nodeindex) { // nodeindex = day
case 1:
case 31:
case 62:
imageContainer.className = "phase_1";
break;
case 9:
case 39:
case 70:
imageContainer.className = "phase_2";
break;
case 17:
case 47:
case 78:
imageContainer.className = "phase_3";
break;
case 25:
case 55:
case 86:
imageContainer.className = "phase_4";
break;
default:
imageContainer.className = ""; // remove image
}
}
}
}
zingchart.bind('myChart', 'guide_mousemove', bindImageOnMove);
/*
* since the height of the chart is dynamic in my example (can resize window for chart size),
* this function is used to dynamically set the height of the moon phase image on chart load (set it once)
*/
zingchart.bind('myChart', 'complete', function(e) {
// assign tooltip to top of graph by getting the bottom charts info
var graphInfo = zingchart.exec('myChart', 'getobjectinfo', {
graphid: 'moonrise_moonset_chart',
object: 'graph'
});
imageContainer = document.getElementById('crosshair-image');
imageContainer.style.top = (graphInfo.y + 10) + 'px';
});
</script>
</body>
</html>
/* Globals */
var MILLISECONDS_IN_A_DAY = 86400000;
var MILLISECONDS_IN_A_HOUR = 3600000;
var MILLISECONDS_IN_A_MINUTE = 60000;
var MILLISECONDS_IN_A_SECOND = 1000;
var START_OF_2016_TIMESTAMP = 1451635200000;
//var START_OF_2017_TIMESTAMP = 1451635200;
var currentYearTimestamp = START_OF_2016_TIMESTAMP;
var currentStringYear = '2016';
var DEGREES_SYMBOL = '°';
var MONTH_LABELS = ['Jan 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Feb 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Mar 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Apr 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'May 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'June 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'July 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Aug 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Sep 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Oct 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Nov 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'Dec 1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']; //length is 366
var SCALE_MARKER_COLOR = '#bdbdbd';
var SCALE_MARKER_LINE_WIDTH = 1;
var SCALE_MARKER_ALPHA = .7;
var SCALE_MARKERS = [ // scale markers used for more precise vertical guidelines
{
type: 'line',
range: [31],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [60],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [91],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [121],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [152],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [182],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [213],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [244],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [274],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [305],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
},
{
type: 'line',
range: [335],
lineColor: SCALE_MARKER_COLOR,
lineWidth: SCALE_MARKER_LINE_WIDTH,
alpha: SCALE_MARKER_ALPHA
}
];
var myConfig = {
layout: '3x1',
graphset: [{ // start sunrise/sunset graph
id: 'sunrise_sunset_chart',
type: 'line',
utc: true,
plot: {
maxTrackers: 1000, // max amount of nodes to be drawn. Also applies event listeners to 1000 nodes so faster if this is off
highlightState: { // legend hover line state
lineWidth: 5
},
marker: { // turn nodes off so its just the line
visible: false
}
},
plotarea: {
margin: '10 80 60 80'
},
legend: {
highlightPlot: true, // hover highlight
marginBottom: 0,
marginRight: 80,
layout: 'h'
},
scaleX: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
labels: MONTH_LABELS,
maxItems: 366, // dont allow more than this amount of items
itemsOverlap: true, // force items to not disappear
step: 'day',
guide: { //hide the guide lines
visible: false
},
tick: { // hide ticks
visible: false
},
transform: {
type: 'date',
all: '%M %d'
},
label: {
text: 'Days'
},
markers: SCALE_MARKERS // these are your new guidelines
},
scaleY: {
minValue: 19800000, // 05:30:00 am
maxValue: 26100000, // 07:15:00 am
step: (MILLISECONDS_IN_A_MINUTE * 15),
maxItems: 8, // force 8 items
itemsOverlap: true, // helps force 8 items
lineColor: '#ff5722',
tick: {
lineColor: '#ff5722'
},
guide: {
visible: false
},
label: {
text: 'Sunrise Time (AM)'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
scaleY2: {
minValue: 63000000, // 05:30:00 pm
maxValue: 69300000, // 07:15:00 pm
step: (MILLISECONDS_IN_A_MINUTE * 15),
maxItems: 8, // force 8 items
itemsOverlap: true, // helps force 8 items
lineColor: '#ff9800',
tick: {
lineColor: '#ff9800'
},
guide: {
visible: false
},
label: {
text: 'Sunset Time (PM)'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
tooltip: {
visible: false
}, // turn off tooltip
crosshairX: {
shared: true,
plotLabel: { // crosshair tooltip
headerText: '%kv ' + currentStringYear,
text: '<span style="color:%color">%t:</span> %vv',
fontSize: 15,
padding: 10,
borderRadius: 5
},
scaleLabel: {
visible: false
}, // highlight x-axis off
marker: { // highlight marker
type: 'circle',
size: 4
}
},
series: []
},
{
id: 'azimuth_chart',
type: 'line',
utc: true,
plot: {
maxTrackers: 1000,
highlightState: {
lineWidth: 5
},
marker: {
visible: false
}
},
plotarea: {
margin: '10 80 60 80'
},
legend: {
highlightPlot: true,
marginBottom: 0,
marginRight: 80,
layout: 'h'
},
scaleX: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
labels: MONTH_LABELS,
maxItems: 366,
itemsOverlap: true,
step: 'day',
guide: {
visible: false
},
tick: {
visible: false
},
transform: {
type: 'date',
all: '%M %d'
},
label: {
text: 'Days'
},
markers: SCALE_MARKERS
},
scaleY: {
values: '60:120:10',
maxItems: 7,
itemsOverlap: true,
format: '%v' + DEGREES_SYMBOL,
guide: {
visible: false
},
lineColor: '#ff5722',
tick: {
lineColor: '#ff5722'
},
label: {
text: 'Sunrise Direction'
}
},
scaleY2: {
values: '240:300:10',
maxItems: 7,
itemsOverlap: true,
format: '%v' + DEGREES_SYMBOL,
guide: {
visible: false
},
lineColor: '#ff9800',
tick: {
lineColor: '#ff9800'
},
label: {
text: 'Sunset Direction'
}
},
tooltip: {
visible: false
},
crosshairX: {
shared: true,
plotLabel: {
headerText: '%kv ' + currentStringYear,
text: '<span style="color:%color">%t:</span> %v' + DEGREES_SYMBOL,
fontSize: 15,
padding: 10,
borderRadius: 5
},
scaleLabel: {
visible: false
},
marker: {
type: 'circle',
size: 4
}
},
series: []
}, // end azimuth graph
{ // start sunrise/sunset graph
id: 'moonrise_moonset_chart',
type: 'line',
utc: true,
plot: {
maxTrackers: 1000,
highlightState: {
lineWidth: 5
},
marker: {
visible: false
}
},
plotarea: {
margin: '10 80 60 80'
},
legend: {
highlightPlot: true,
marginBottom: 0,
marginRight: 80,
layout: 'h'
},
scaleX: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
labels: MONTH_LABELS,
maxItems: 366,
itemsOverlap: true,
step: 'day',
guide: {
visible: false
},
tick: {
visible: false
},
transform: {
type: 'date',
all: '%M %d'
},
label: {
text: 'Days'
},
markers: SCALE_MARKERS
},
scaleX2: {
minValue: currentYearTimestamp,
maxValue: currentYearTimestamp + (MILLISECONDS_IN_A_DAY * 365),
maxItems: 400,
itemsOverlap: true,
step: 'day',
guide: {
visible: false
},
tick: {
visible: false
},
},
scaleY: {
minValue: 0,
maxValue: MILLISECONDS_IN_A_DAY,
step: MILLISECONDS_IN_A_HOUR * 2,
maxItems: 7,
itemsOverlap: true,
guide: {
visible: false
},
label: {
text: 'Moonrise Time'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
scaleY2: {
minValue: 0,
maxValue: MILLISECONDS_IN_A_DAY,
step: MILLISECONDS_IN_A_HOUR * 2,
maxItems: 7,
itemsOverlap: true,
guide: {
visible: false
},
label: {
text: 'Moonset Time'
},
transform: {
type: 'date',
all: '%h:%i %A'
}
},
tooltip: {
visible: false
},
crosshairX: {
shared: true,
plotLabel: {
headerText: '%kv ' + currentStringYear,
text: '<span style="color:%color">%t:</span> %vv',
fontSize: 15,
padding: 10,
borderRadius: 5,
},
scaleLabel: {
visible: false
},
marker: {
type: 'circle',
size: 4
}
},
series: []
}
]
};
zingchart.render({
id: 'myChart',
data: myConfig,
height: '100%',
width: '100%'
});
/*
*
* Example time: "113.63°"
*/
function parseDegrees(sDegrees) {
return Number(sDegrees.slice(0, sDegrees.length - 1));
}
/*
*
* Example time: "1, 5:50:56 AM"
* returns milliseconds value
*/
function parseStringToTimestamp(sTime) {
if (sTime.length <= 1) {
return null; // null value if blank cell
}
/*
* Y axis are plotted from 0 - 24hrs in milliseconds.
* Since x-axis already plots day we don't need the exact timestamp
* for this day of the year. Just the offset in milliseconds.
*
*/
var aTime = sTime.split(/\s/); // separate am/pm
parsedTime = aTime[0]; // asign time
parsedTime = parsedTime.split(':'); // split time
if (aTime[1].toLowerCase() == 'pm') {
if (Number(parsedTime[0]) !== 12)
parsedTime[0] = Number(parsedTime[0]) + 12;
} else { // if am
if (Number(parsedTime[0]) === 12)
parsedTime[0] = 0; // convert 12:00AM to Unix time
}
parsedTime[0] = Number(parsedTime[0]) * MILLISECONDS_IN_A_HOUR;
parsedTime[1] = Number(parsedTime[1]) * MILLISECONDS_IN_A_MINUTE;
parsedTime[2] = Number(parsedTime[2]) * MILLISECONDS_IN_A_SECOND;
return parsedTime[0] + parsedTime[1] + parsedTime[2];
}
function parseDataAndRenderChart() {
var arrayOfRows = this.responseText.split('\n');
var azimuthSeries = [];
var sunriseSunsetSeries = [];
var moonriseMoonsetSeries = [];
// turn rows as a string into an array
arrayOfRows = arrayOfRows.map(function(obj) {
return obj.split(',');
});
// initialize azimuth chart series
azimuthSeries[0] = {
text: 'Sunrise Direction',
values: [],
scales: 'scale-x, scale-y',
lineColor: '#ff5722'
};
azimuthSeries[1] = {
text: 'Sunset Direction',
values: [],
scales: 'scale-x, scale-y-2',
lineColor: '#ff9800'
};
// initialize sunrise/sunset chart series
sunriseSunsetSeries[0] = {
text: 'Sunrise',
values: [],
scales: 'scale-x, scale-y',
lineColor: '#ff5722'
};
sunriseSunsetSeries[1] = {
text: 'Sunset',
values: [],
scales: 'scale-x, scale-y-2',
lineColor: '#ff9800'
};
// initialize moonrise/moonset chart series
moonriseMoonsetSeries[0] = {
text: 'Moonrise',
values: [],
scales: 'scale-x, scale-y',
lineColor: '#9e9e9e'
};
moonriseMoonsetSeries[1] = {
text: 'Moonset',
values: [],
scales: 'scale-x, scale-y-2',
lineColor: '#212121'
};
/*
* ignore arrayOfRows[0] those are the titles
* assign millisecond timestamp value and string value to float
* [[]] array of arrays
*/
for (var i = 1; i < arrayOfRows.length; i++) {
// compile azimuth data
azimuthSeries[0].values.push( // push to series 0
parseDegrees(arrayOfRows[i][2])
);
azimuthSeries[1].values.push( // push to series 1
parseDegrees(arrayOfRows[i][4])
);
// compile sunrise/sunset data
sunriseSunsetSeries[0].values.push( // push to series 0
parseStringToTimestamp(arrayOfRows[i][1])
);
sunriseSunsetSeries[1].values.push( // push to series 1
parseStringToTimestamp(arrayOfRows[i][3])
);
// compile moonrise/moonset data
moonriseMoonsetSeries[0].values.push( // push to series 0
parseStringToTimestamp(arrayOfRows[i][5])
);
moonriseMoonsetSeries[1].values.push( // push to series 1
parseStringToTimestamp(arrayOfRows[i][6])
);
}
// load data into chart
zingchart.exec('myChart', 'setseriesdata', {
graphid: 'azimuth_chart',
update: false,
data: azimuthSeries
});
zingchart.exec('myChart', 'setseriesdata', {
graphid: 'sunrise_sunset_chart',
update: false,
data: sunriseSunsetSeries
});
zingchart.exec('myChart', 'appendseriesdata', {
graphid: 'moonrise_moonset_chart',
update: false,
data: moonriseMoonsetSeries
});
zingchart.exec('myChart', 'update');
}
// request data from url
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", parseDataAndRenderChart);
oReq.open("GET", 'https://app.zingsoft.com/api/file/GSNQC6UG/xqIP1B9ZRXiOmCCmLfCU_sun_moon_rise_azimuth.csv'); // exported google sheets to csv (thats it)
oReq.send();
/*
* Event listener for crosshair and how to change image.
* This will change the class (check CSS tab) on the div (positioned absolute over the chart)
* it will update the image based on the day (nodeindex).
*
* eg. nodeindex 32 is february 1st
*/
var imageContainer = null;
function bindImageOnMove(e) {
// event is fired for all 3 graphs. For speed only do this once.
if (e.graphid === "moonrise_moonset_chart") {
// easy way to cache the reference to the element
imageContainer = (imageContainer != null) ? imageContainer : document.getElementById('crosshair-image');
// used CSS modifications over chart modifcations for faster speed.
imageContainer.style.left = e.guide.x + 'px';
// if node index exists for at least one plot
if (e.items[0] && e.items[0].nodeindex) {
switch (e.items[0].nodeindex) { // nodeindex = day
case 1:
case 31:
case 62:
imageContainer.className = "phase_1";
break;
case 9:
case 39:
case 70:
imageContainer.className = "phase_2";
break;
case 17:
case 47:
case 78:
imageContainer.className = "phase_3";
break;
case 25:
case 55:
case 86:
imageContainer.className = "phase_4";
break;
default:
imageContainer.className = ""; // remove image
}
}
}
}
zingchart.bind('myChart', 'guide_mousemove', bindImageOnMove);
/*
* since the height of the chart is dynamic in my example (can resize window for chart size),
* this function is used to dynamically set the height of the moon phase image on chart load (set it once)
*/
zingchart.bind('myChart', 'complete', function(e) {
// assign tooltip to top of graph by getting the bottom charts info
var graphInfo = zingchart.exec('myChart', 'getobjectinfo', {
graphid: 'moonrise_moonset_chart',
object: 'graph'
});
imageContainer = document.getElementById('crosshair-image');
imageContainer.style.top = (graphInfo.y + 10) + 'px';
});