-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #79 from ede0m/thumbnail_FV_34
Thumbnail fv_34 with JSDOM
- Loading branch information
Showing
8 changed files
with
371 additions
and
5 deletions.
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,6 @@ | |
'height': '4', | ||
'width': '7.5' | ||
} | ||
|
||
# Thumbnail Support | ||
THUMBNAIL = False |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,5 +32,4 @@ | |
|
||
</body> | ||
|
||
</html> | ||
|
||
</html> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
|
||
'use strict'; | ||
/** | ||
* @param {Object} options - holds options for the configuration of the hydrograph | ||
* Non-optional Keys include: | ||
* @prop 'height' v(int) - height of the graph | ||
* @prop 'width' v(int) - width of the graph | ||
* @prop 'data' v(list) - A list of objects representing data points | ||
* @prop 'div_id' v(string) - id for the container for this graph | ||
* | ||
* hydromodule is a module for creating hydrographs using d3. Pass it a javascript object | ||
* specifying config options for the graph. Call init() to create the graph. Linked | ||
* interaction functions for other figures should be passed to init in and object. | ||
* | ||
*/ | ||
var hydromodule = function (options) { | ||
|
||
var self = {}; | ||
|
||
var margin = {top: 30, right: 20, bottom: 30, left: 50}; | ||
var height = options.height - margin.top - margin.bottom; | ||
var width = options.width - margin.left - margin.right; | ||
|
||
// Adds the svg canvas | ||
var svg = null; | ||
// Focus for hydrograph hover tooltip | ||
var focus = null; | ||
// Voronoi layer | ||
var voronoi_group = null; | ||
// Define the voronoi | ||
var voronoi = d3.voronoi() | ||
.x(function (d) { | ||
return x(d.time_mili); | ||
}) | ||
.y(function (d) { | ||
return y(d.value); | ||
}) | ||
.extent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]); | ||
// Define the line | ||
var line = d3.line() | ||
.x(function (d) { | ||
return x(d.time_mili); | ||
}) | ||
.y(function (d) { | ||
return y(d.value); | ||
}); | ||
// Set the ranges | ||
var x = d3.scaleTime().range([0, width]); | ||
var y = d3.scaleLog().range([height, 0]); | ||
|
||
/** | ||
* Filters a set of data based on the ids listed in display_ids | ||
* @returns {Array} The entries of the original `data` whose `key` values are elements of display_ids. | ||
*/ | ||
var subset_data = function (full_data) { | ||
var toKeep = []; | ||
full_data.forEach(function (d) { | ||
if (options.display_ids.indexOf(d.key) !== -1) { | ||
toKeep.push(d); | ||
} | ||
}); | ||
return toKeep; | ||
}; | ||
/** | ||
* | ||
* Draws the svg, scales the range of the data, and draws the line for each site | ||
* all based on the data set as it was passed in. Called as needed | ||
* when data changes (as in removal of a line). | ||
* | ||
*/ | ||
var update = function () { | ||
|
||
// Cut the data down to sites we want to display | ||
var sub_data = subset_data(options.data); | ||
// Remove the current version of the graph if one exists | ||
var current_svg = d3.select(options.div_id + ' svg'); | ||
if (current_svg) { | ||
current_svg.remove(); | ||
} | ||
// recreate svg | ||
svg = d3.select(options.div_id) | ||
.append('svg') | ||
.attr('width', width + margin.left + margin.right) | ||
.attr('height', height + margin.top + margin.bottom) | ||
.append('g') | ||
.attr('transform', | ||
'translate(' + margin.left + ',' + margin.top + ')'); | ||
|
||
var graph_data = sub_data.map(function (d) { | ||
return { | ||
'date': d.date, | ||
'key': d.key, | ||
'name': d.name, | ||
'time': d.time, | ||
'time_mili': d.time_mili, | ||
'timezone': d.timezone, | ||
'value': Number(d.value) | ||
}; | ||
}); | ||
|
||
// Scale the range of the data | ||
x.domain(d3.extent(graph_data, function (d) { | ||
return d.time_mili; | ||
})); | ||
y.domain([d3.min(graph_data, function (d) { | ||
return d.value; | ||
}), d3.max(graph_data, function (d) { | ||
return d.value; | ||
})]); | ||
// Nest the entries by site number | ||
var dataNest = d3.nest() | ||
.key(function (d) { | ||
return d.key; | ||
}) | ||
.entries(graph_data); | ||
// Loop through each symbol / key | ||
dataNest.forEach(function (d) { | ||
svg.append('g') | ||
.attr('class', 'hydro-inactive') | ||
.append('path') | ||
.attr('id', 'hydro' + d.key) | ||
.attr('d', line(d.values)); | ||
console.log('Here IN DATANEST'); | ||
}); | ||
// Add the X Axis | ||
svg.append('g') | ||
.attr('class', 'axis') | ||
.attr('transform', 'translate(0,' + height + ')') | ||
.call(d3.axisBottom(x).tickFormat(d3.timeFormat('%B %e'))); | ||
|
||
// Add the Y Axis | ||
svg.append('g') | ||
.attr('class', 'axis') | ||
.call(d3.axisLeft(y).ticks(10, '.0f')); | ||
|
||
// Tooltip | ||
focus = svg.append('g') | ||
.attr('transform', 'translate(-100,-100)') | ||
.attr('class', 'focus'); | ||
focus.append('circle') | ||
.attr('r', 3.5); | ||
|
||
focus.append('text') | ||
.attr('y', -10); | ||
|
||
// Voronoi Layer | ||
voronoi_group = svg.append('g') | ||
.attr('class', 'voronoi'); | ||
voronoi_group.selectAll('path') | ||
.data(voronoi.polygons(d3.merge(dataNest.map(function (d) { | ||
return d.values | ||
})))) | ||
.enter().append('path') | ||
.attr('d', function (d) { | ||
return d ? 'M' + d.join('L') + 'Z' : null; | ||
}) | ||
.on('mouseover', function (d) { | ||
self.linked_interactions.hover_in(d.data.name, d.data.key); | ||
self.activate_line(d.data.key); | ||
self.series_tooltip_show(d); | ||
}) | ||
.on('mouseout', function (d) { | ||
self.linked_interactions.hover_out(); | ||
self.deactivate_line(d.data.key); | ||
self.series_tooltip_remove(d.data.key); | ||
}) | ||
.on('click', function (d) { | ||
self.linked_interactions.click(d.data.key); | ||
self.remove_series(d.data.key); | ||
}); | ||
|
||
}; | ||
|
||
/** | ||
* Initialize the Hydrograph. | ||
* | ||
*@param {Object} linked_interactions - Object holding functions that link to another figure's interactions. | ||
* Pass null if there are no such interactions to link. | ||
* @prop 'hover_in' - linked interaction function for hover_in events on this figure. | ||
* @prop 'hover_out' - linked interaction function for hover_out events on this figure. | ||
* @prop 'click' - linked interaction function for click events on this figure. | ||
* | ||
* | ||
*/ | ||
self.init = function (linked_interactions) { | ||
self.linked_interactions = linked_interactions; | ||
update(); | ||
return self; | ||
}; | ||
|
||
/** | ||
* Returns the svg element node. Primarily used for thumb-nailing. | ||
*/ | ||
self.get_svg_elem = function () { | ||
return d3.select(options.div_id); | ||
}; | ||
/** | ||
* Displays tooltip for hydrograph at a data point in addition to | ||
* corresponding map site tooltip. | ||
*/ | ||
self.series_tooltip_show = function (d) { | ||
focus.attr('transform', 'translate(' + x(d.data.time_mili) + ',' + y(d.data.value) + ')'); | ||
focus.select('text').html(d.data.key + ': ' + d.data.value + ' cfs ' + ' ' + d.data.time + ' ' + d.data.timezone); | ||
}; | ||
|
||
/** | ||
* Removes tooltip view from the hydrograph series | ||
* as well as the correspond mapsite tooltip. | ||
*/ | ||
self.series_tooltip_remove = function (sitekey) { | ||
focus.attr('transform', 'translate(-100,-100)'); | ||
}; | ||
|
||
/** | ||
* Removes a line from the hydrograph. This resizes data | ||
* appropriately and removes accents from the corresponding | ||
* site on the map. | ||
*/ | ||
self.remove_series = function (sitekey) { | ||
var keep_ids = FV.hydrograph_display_ids; | ||
keep_ids.splice(FV.hydrograph_display_ids.indexOf(sitekey), 1); | ||
self.change_lines(keep_ids); | ||
}; | ||
/** | ||
* Update the value of display_ids and call update to redraw the graph to match. | ||
* @param new_display_ids The new set of gages to be displayed. | ||
*/ | ||
self.change_lines = function (new_display_ids) { | ||
FV.hydrograph_display_ids = new_display_ids; | ||
update(); | ||
}; | ||
/** | ||
* Highlight a line. | ||
* @param sitekey the site number of the line to be highlighted | ||
*/ | ||
self.activate_line = function (sitekey) { | ||
d3.select('#hydro' + sitekey).attr('class', 'hydro-active'); | ||
}; | ||
/** | ||
* Un-highlight a line | ||
* @param sitekey the site number of the line to be un-highlighted | ||
*/ | ||
self.deactivate_line = function (sitekey) { | ||
d3.select('#hydro' + sitekey).attr('class', 'hydro-inactive'); | ||
}; | ||
|
||
return self | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* | ||
* This Script is intended to be run after a flask freeze during the build process. | ||
* | ||
* Its main objective is to dynamically create thumbnails for the site figures | ||
* based on the data obtained from our server side flask services. | ||
* | ||
* */ | ||
|
||
|
||
|
||
// Dependency Import | ||
var fs = require('fs'); | ||
var jsdom = require('jsdom/lib/old-api.js'); | ||
var svg2png = require('svg2png'); | ||
// Data imports | ||
var data_hydro = require('../thumbnail/hydrograph_data.json'); | ||
|
||
// Collect script arguments for external css | ||
var style_path = null; | ||
var args = process.argv.splice(process.execArgv.length + 2); | ||
if (args.length > 2) { | ||
console.log('\nUsage: node thumbnail.js ' + | ||
'\n\nOptional flag: -css path/to/css/file.css\n'); | ||
process.exit(); | ||
} else { | ||
if (args[0] === '-css') { | ||
style_path = args[1]; | ||
} else { | ||
console.log('\nUnrecognized argument: ' + args[0] + '\n'); | ||
process.exit(); | ||
} | ||
} | ||
|
||
// Headless Browser Start for DOM | ||
jsdom.env( | ||
|
||
// create DOM hook | ||
"<html><body><div id='hydrograph'></div>" + | ||
"<div id='map'></divid>" + | ||
"</body></html>", | ||
|
||
// load local assets into window environment | ||
[ | ||
'./floodviz/static/bower_components/d3/d3.js', | ||
'./floodviz/static/bower_components/proj4/dist/proj4.js', | ||
'./floodviz/thumbnail/hydro_thumbnail.js' | ||
], | ||
|
||
function (err, window) { | ||
var hydro_figure = window.hydromodule( | ||
{ | ||
'height': 300, | ||
'width': 560, | ||
'div_id': '#hydrograph', | ||
'data': data_hydro, | ||
"display_ids": ['05471200', '05476750', '05411850', '05454220', | ||
'05481950', '05416900', '05464500', '05487470'] | ||
// Refactor Later. I'm assuming this will change with references.json | ||
} | ||
); | ||
convert(hydro_figure,window, 'floodviz/static/css/hydrograph.css', 'floodviz/thumbnail/thumbnail_hydro.png'); | ||
} | ||
); | ||
|
||
// Wrapper around svg2png that injects custom css to inline svg before conversion | ||
function convert(figure, window, css_path, filename) { | ||
var style_ext = null; | ||
var svg_string = null; | ||
var svg = figure.get_svg_elem().node(); | ||
var style_default = fs.readFileSync(css_path, 'utf8'); | ||
figure.init(); | ||
if (style_path !== null) { | ||
try { | ||
style_ext = fs.readFileSync(style_path, 'utf8'); | ||
} catch(error) { | ||
console.log('\nError: external css file path not found.\nUsing only default style.\n\n' + error); | ||
style_ext = null; | ||
} | ||
svg_string = inject_style(style_default, style_ext, svg, window); | ||
} else { | ||
svg_string = inject_style(style_default, null, svg, window); | ||
} | ||
// Takes care of canvas conversion and encodes base64 | ||
svg2png(svg_string) | ||
.then(buffer => fs.writeFile(filename, buffer)) | ||
.then(console.log('\nConverted D3 figure to PNG successfully... \n')) | ||
.catch(e => console.error(e)); | ||
} | ||
|
||
// Hook style to inline svg string. | ||
function inject_style(style_string, ext_style, svgDomElement, window) { | ||
var s = window.document.createElement('style'); | ||
s.setAttribute('type', 'text/css'); | ||
s.innerHTML = "<![CDATA[\n" + style_string + ext_style + "\n]]>"; | ||
var defs = window.document.createElement('defs'); | ||
defs.appendChild(s); | ||
svgDomElement.insertBefore(defs, svgDomElement.firstChild); | ||
return svgDomElement.parentElement.innerHTML; | ||
} |
Oops, something went wrong.