Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thumbnail fv_34 with JSDOM #79

Merged
merged 26 commits into from
Jul 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1692bee
merge upstream
Jul 19, 2017
c2d12c7
a start. IMPLEMENT
Jul 20, 2017
ffd2fed
scrapy stuff here atm, but the svg string export is working. Will nee…
Jul 20, 2017
2f6438d
merge upstream
Jul 21, 2017
9375729
getting further .. I guess. Tried using svg2png and canvg and neither…
Jul 21, 2017
6df16df
moving to use phantomjs
Jul 25, 2017
bef9932
Somthing working! Reverted back to jsdom
Jul 25, 2017
5ea65ec
use jsdom to pass window to hydromodule thumbnail
Jul 25, 2017
8a05655
rivers wasnt worked on. Pesky rivers
Jul 25, 2017
1064c9f
hydrograph.html not relevant
Jul 25, 2017
a40807a
implementing map thumbnail ability
Jul 26, 2017
4ab507d
Merge branch 'master' of https://github.com/USGS-VIZLAB/active-flood-…
Jul 26, 2017
b1bee7a
added support for map png
Jul 26, 2017
8c20cbb
directory spelling
Jul 26, 2017
bf9ea85
directory names in path
Jul 26, 2017
bcaa845
moved conversion logic to function
Jul 27, 2017
26d0ff9
added argument parser to avoid overlapping png images
Jul 27, 2017
9daea30
exit if invalid arguments
Jul 27, 2017
b2dc43f
added svg2png to package.json depenecies
Jul 27, 2017
62e7091
config var for writing flat files
Jul 28, 2017
a14baeb
Merge branch 'master' into thumbnail_FV_34
Jul 28, 2017
51456c7
added argument parser and inline style injecttion for external css
Jul 28, 2017
0ae52f9
Merge branch 'thumbnail_FV_34' of https://github.com/ede0m/active-flo…
Jul 28, 2017
1d76943
removed map code. added error handling for external css bad file path
Jul 28, 2017
d1574db
removed some unnecessary code
Jul 31, 2017
7e7b94e
spaces
Jul 31, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,7 @@ node_modules/

*floodviz/static/geojson/*
!states.json

# Thumbnail Files #
*floodviz/thumbnail/*.json
*floodviz/thumbnail/*.png
3 changes: 3 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
'scale': None,
}

# Thumbnail Support
THUMBNAIL = False

deployed_url_base = os.environ.get('DEPLOYED_BASE_URL')
if deployed_url_base:
FREEZER_BASE_URL = deployed_url_base
Expand Down
3 changes: 3 additions & 0 deletions examples/iowa.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
'height': '4',
'width': '7.5'
}

# Thumbnail Support
THUMBNAIL = False
3 changes: 1 addition & 2 deletions floodviz/templates/hydrograph.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,4 @@

</body>

</html>

</html>
248 changes: 248 additions & 0 deletions floodviz/thumbnail/hydro_thumbnail.js
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
};
100 changes: 100 additions & 0 deletions floodviz/thumbnail/thumbnail.js
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you start with a brief comment describing what this script does

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;
}
Loading