How to batch similar gulp tasks to reduce code repetition - gulp

I have written a gulp file that watches over several directories for changes, and then create concatenation to multiple specified destination.
Here is a simplified version of my project structure:
I have 2 site folders:
one/ and two/
Each site have two branch folders:
a/ and b/
Inside each branch, there are three folders:
inner/, outer/ and web/
My task is to grab similar part files from the inner and outer folders, and concatenate them into relative web folders. Below is a simple example of desired output.
-- inner/
|-- color1
|-- color2
|-- fruit1
|-- fruit2
-- outer/
|-- color1
|-- color2
|-- fruit1
|-- fruit2
-- web/
|-- colors.txt
|-- fruits.txt
I have created a config.json file to hold site specific configuration. Currently only using it to customize site paths. Here is the config.json
{
"sites": {
"one": {
"a": "/path/to/one/a/",
"b": "/path/to/one/b/"
},
"two": {
"a": "/path/to/two/a/",
"b": "/path/to/two/b/"
}
}
}
And finally here is the gulpfile.js
// Include local Gulp
var gulp = require("gulp");
// Get data from config.json
var sites = require("./config.json").sites;
// Include Gulp specific plugins
var gConcat = require("gulp-concat");
var gHeader = require("gulp-header");
var gUtil = require("gulp-util");
var gNotify = require("gulp-notify");
// Setup directories
var outer = "outer/";
var inner = "inner/";
var web = "web/";
// Misc
var alertMessage = "# GENERATED FILE - DO NOT MODIFY\n\n";
// 8 total tasks for concatenation
// Concatenate to colors.txt - 4 tasks
// Color task 1: [ Site => one ] [ Branch => a ]
gulp.task("one_a_color", function() {
return gulp.src([sites.one.a + outer + "color?", sites.one.a + inner + "color?"])
.pipe(gConcat("colors.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.one.a + web))
.pipe(gNotify());
});
// Color task 2: [ Site => one ] [ Branch => b ]
gulp.task("one_b_color", function() {
return gulp.src([sites.one.b + outer + "color?", sites.one.b + inner + "color?"])
.pipe(gConcat("colors.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.one.b + web))
.pipe(gNotify());
});
// Color task 3: [ Site => two ] [ Branch => a ]
gulp.task("two_a_color", function() {
return gulp.src([sites.two.a + outer + "color?", sites.two.a + inner + "color?"])
.pipe(gConcat("colors.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.two.a + web))
.pipe(gNotify());
});
// Color task 4: [ Site => two ] [ Branch => b ]
gulp.task("two_b_color", function() {
return gulp.src([sites.two.b + outer + "color?", sites.two.b + inner + "color?"])
.pipe(gConcat("colors.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.two.b + web))
.pipe(gNotify());
});
// Concatenate to fruits.txt - 4 tasks
// Fruit task 1: [ Site => one ] [ Branch => a ]
gulp.task("one_a_fruit", function() {
return gulp.src([sites.one.a + outer + "fruit?", sites.one.a + inner + "fruit?"])
.pipe(gConcat("fruits.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.one.a + web))
.pipe(gNotify());
});
// Fruit task 2: [ Site => one ] [ Branch => b ]
gulp.task("one_b_fruit", function() {
return gulp.src([sites.one.b + outer + "fruit?", sites.one.b + inner + "fruit?"])
.pipe(gConcat("fruits.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.one.b + web))
.pipe(gNotify());
});
// Fruit task 3: [ Site => two ] [ Branch => a ]
gulp.task("two_a_fruit", function() {
return gulp.src([sites.two.a + outer + "fruit?", sites.two.a + inner + "fruit?"])
.pipe(gConcat("fruits.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.two.a + web))
.pipe(gNotify());
});
// Fruit task 4: [ Site => two ] [ Branch => b ]
gulp.task("two_b_fruit", function() {
return gulp.src([sites.two.b + outer + "fruit?", sites.two.b + inner + "fruit?"])
.pipe(gConcat("fruits.txt"))
.pipe(gHeader(alertMessage))
.pipe(gulp.dest(sites.two.b + web))
.pipe(gNotify());
});
// Watch for all events in specified {directories}/{files}, then trigger appropriate task
// 8 total watch jobs
gulp.task("watch", function () {
// Color related watch jobs - Total 4
// Color watch 1: [ Site => one ] [ Branch => a ]
gulp.watch([sites.one.a + outer + "**/color?", sites.one.a + inner + "**/color?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("one_a_color");
});
// Color watch 2: [ Site => one ] [ Branch => b ]
gulp.watch([sites.one.b + outer + "**/color?", sites.one.b + inner + "**/color?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("one_b_color");
});
// Color watch 3: [ Site => two ] [ Branch => a ]
gulp.watch([sites.two.a + outer + "**/color?", sites.two.a + inner + "**/color?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("two_a_color");
});
// Color watch 4: [ Site => two ] [ Branch => b ]
gulp.watch([sites.one.b + outer + "**/color?", sites.one.b + inner + "**/color?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("two_b_color");
});
// Fruit related watch jobs - Total 4
// Fruit watch 1: [ Site => one ] [ Branch => a ]
gulp.watch([sites.one.a + outer + "**/fruit?", sites.one.a + inner + "**/fruit?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("one_a_fruit");
});
// Fruit watch 2: [ Site => one ] [ Branch => b ]
gulp.watch([sites.one.b + outer + "**/fruit?", sites.one.b + inner + "**/fruit?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("one_b_fruit");
});
// Fruit watch 3: [ Site => two ] [ Branch => a ]
gulp.watch([sites.two.a + outer + "**/fruit?", sites.two.a + inner + "**/fruit?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("two_a_fruit");
});
// Fruit watch 4: [ Site => two ] [ Branch => b ]
gulp.watch([sites.one.b + outer + "**/fruit?", sites.one.b + inner + "**/fruit?"], function(event) {
gUtil.log(event.path.split("/").pop(), "=>", event.type);
gulp.start("two_b_fruit");
});
});
// Run all tasks
gulp.task("background",
[
"one_a_color", "one_b_color", "two_a_color", "two_b_color",
"one_a_fruit", "one_b_fruit", "two_a_fruit", "two_b_fruit",
"watch"
]
);
The above gulp file works and does the job. However, as you can see, most of the codes are repeated, only part that changes are the gulp.src and gulp.dest, along with the task names.
My question is. Would it be possible to simplify this gulp file, so instead of repeating codes for every tasks, maybe similar tasks can be batched together.

Not that easy a task, but let's see if we can optimise that. Gulp and Globs greatly deal with arrays, that's why we have to convert your paths to an array first:
var gulp = require('gulp');
var concat = require('gulp-concat');
var es = require('event-stream');
var sites = require('./config.json').sites;
var toArray = function(conf) {
var arr = [];
for(var key in conf) {
if(typeof conf[key] === 'object') {
arr = arr.concat(toArray(conf[key]));
} else {
arr.push(conf[key]);
}
}
return arr;
};
var sites = toArray(sites);
Now that we have the paths, we create the globs for fruits and colors.
var globs = [];
sites.forEach(function(data) {
globs.push(data + '**/color*');
globs.push(data + '**/fruit*');
});
With your current config, you get an array of 8 entries. Next, let us define the concat-task. Here is what you mean with "batched" together, we need a so called stream array (I wrote about that here). It's a simple mapping of an existing array to many gulp streams, which are merged at the end via the event-stream module. With the color/fruit thing going on, we need to be a little creative with our concat names and dest names.
Note that I use the changed plugin to prevent useless builds.
gulp.task('concat', function() {
var tasks = globs.map(function(glob) {
var file = glob.indexOf('color') >= 0 ? 'col' : 'fru';
var dest = glob.replace('**/color*','').replace('**/fruit*','') + 'web';
return gulp.src(glob)
.pipe(concat(file + '.txt'))
.pipe(gulp.dest(dest))
});
return es.merge.apply(null, tasks);
});
This task now does everything we need, and incrementally so. So our watch process is rather straightforward.
gulp.task('watch', ['concat'], function() {
gulp.watch(globs, ['concat']);
});
Hope this helps!
Update
Alright, I made some adaptations, which should prevent having your whole project rebuilt.
First, I extracted the concatStream to a function. This is actually the one thing you already did with your own sample:
var concatStream = function(glob) {
var file = glob.indexOf('color') >= 0 ? 'farbe' : 'frucht';
var dest = glob.replace('**/color*','').replace('**/fruit*','') + 'web';
return gulp.src(glob)
.pipe(concat(file + '.txt'))
.pipe(header(alertMessage))
.pipe(notify())
.pipe(gulp.dest(dest))
};
Depending on the Glob (the file pattern we select either colors or fruits from our directories), we define a new output (file, is 'col' when 'color' is in our search string, 'fru' otherwise) and a new destination (which is just the old folder without the colors/fruits search pattern).
gulp.task('concat') does now the following:
gulp.task('concat', function() {
var tasks = globs.map(concatStream);
return es.merge.apply(null, tasks);
});
Each of our globs (console.log them, if you want to know what's in there) gets mapped to the concatStream, then the new array of streams gets merged and executed.
The watch task is now new... we do kinda the same as with our 'concat' task:
gulp.task('watch', ['concat'], function() {
globs.map(function(glob) {
gulp.watch(glob, function() {
return concatStream(glob);
})
})
});
For each glob, we create a new watcher, which just calls the concatStream again.
Update
Small change
Inside glob, changing the wildcard (*) to an optional single character match (?), will allow us to use the same name for output file (ex. color and fruit).
var globs = [];
sites.forEach(function(data) {
globs.push(data + '**/color?');
globs.push(data + '**/fruit?');
});
And this as well...
var concatStream = function(glob) {
var file = glob.indexOf('color') >= 0 ? 'color' : 'fruit';
var dest = glob.replace('**/color?','').replace('**/fruit?','') + 'web';
return gulp.src(glob)
.pipe(concat(file + '.txt'))
.pipe(header(alertMessage))
.pipe(notify())
.pipe(gulp.dest(dest))
};
Now I can keep the names of color and fruit for my output file, without worrying bout glob matching the name and adding its existing content back onto the file

Related

Unexpected Error: write callback called multiple times executing Gulp.js tasks inside a loop

I'm trying to execute some gulp tasks inside a map loop to generate css files for each sass theme I defined.
Finally, I return all merged tasks with merge-stream package.
Unfortunately, callback seems sent for each iteration / gulp task of my loop and i'm getting the following message :
Error: write callback called multiple times
#Task('build')
build() {
var self = this;
var tasks = this.themes.map((theme) => {
this.foldersXml = getAttributes('jntFolderWithExternalProvider');
this.filesXml = getAttributes('jntFolder');
this.foldersXml[theme] = []; this.foldersXml[theme].push(getAttributes('jntFolderWithExternalProvider'));
this.filesXml[theme] = []; this.filesXml[theme].push(getAttributes('jntFolderWithExternalProvider'));
fs.readFile(this.themesFolder + '/' + theme + '/' + this.fileName, 'utf8', (err: Error, data: string & Buffer) => {
if (err) {
throw new gutil.PluginError({
plugin: 'build',
message: 'Main file doesn\'t exist for the theme: ' + theme
});
} else {
var vars = data.match(/\$(.*?)\:/g);
this.requiredVars.map(requiredVar => {
if(vars !== null){
if(!vars.contains(requiredVar)){
throw new gutil.PluginError({
plugin: 'build',
message: 'Required variable ' + requiredVar + ' is not defined'
});
};
}
});
}
});
return gulp.src(this.templatesFolder + '/' + this.pattern)
.pipe(header('#import \'' + this.themesFolder + '/' + theme + '/' + this.fileName + '\';'))
.pipe(sass.sync().on('error', gutil.log))
.pipe(rename(function (path: any) {
var file = path.basename + path.extname;
var folderXml = getAttributes('jntFolderWithExternalProvider') as any;
folderXml[file] = [ getAttributes('jntCssFile') ];
self.foldersXml[theme][0][file] = []; self.foldersXml[theme][0][file].push(folderXml);
var fileXml = getAttributes('jntFolderWithExternalProvider') as any;
fileXml['jcr:content'] = [ getAttributes('jntRessource') ];
self.filesXml[theme][0][file] = []; self.filesXml[theme][0][file].push(fileXml);
path.dirname += '/' + file;
}))
.pipe(gulp.dest(this.themesFolder + '/' + theme))
});
return merge(tasks);
}
#SequenceTask()
run(){
return ['build'];
}
I just want to get rid of this message and be sure that callback is invoked only at the end. What's the best approach for you ?

Splice path with Gulp-data

How do I get the name of the parent folder using gulp-data? Currently I'm using the following:
In my front matter
---
title: 'some title'
----
from my gulp file:
function fm2json() {
return gulp.src('src/pages/**/*.html')
.pipe(require('gulp-gray-matter')())
.pipe($.data(function(file){
file.data.relative = file.relative,
file.data.basename = file.basename,
}))
.pipe($.pluck('data', 'new.json'))
.pipe($.data(function(file){
file.contents = new Buffer(JSON.stringify(file.data))
}))
.pipe(require('gulp-json-format')(2))
.pipe(gulp.dest('src/data'));
}
which outputs the following to new.json
[
{
"title":"some title"
"relative":"lesson01\\file.html"
"basename":"file.html"
},
{
"title":"some title 2"
"relative":"lesson02\\file2.html"
"basename":"file2.html"
}
]
I can't figure out how to just get the parent folder of the file so that relative would be "relative":"lesson01" and "relative":"lesson02".
It's not the most efficient way to do it. If it helps anyone this is what I ended up with.
function fm2json() {
return gulp.src('src/pages/**/*.html')
.pipe(require('gulp-gray-matter')())
.pipe($.data(function(file){
// What I ended up with
var relpath = file.relative;
var path = relpath.replace(/\\/g,"/"); //flip slashes
var split = path.split('/'); //split the path into an array
var parent = split[split.length - 2]; // find the array position
file.data.parent = parent,
file.data.file = file.basename,
file.data.path = path,
}))
.pipe($.pluck('data', 'new.json'))
.pipe($.data(function(file){
file.contents = new Buffer(JSON.stringify(file.data))
}))
.pipe(require('gulp-json-format')(2))
.pipe(gulp.dest('src/data'));
}

Renaming a file with gulp rename and adding an index

I need to rename a batch of photos adding an index to them, like 'image-1-tmb' or 'image-23-tmb'. I have already searched this and didn't find it, didn't even come close to finding it.
This is my actual code:
gulp.task('rsz_tmb_menu',function(){
return gulp.src('./zips/**/*.{jpg,JPG}', { base: './zips' })
.pipe(imageResize({
width : width_tmb_menu,
height : height_tmb_menu,
crop : true,
quality : 0.6,
imageMagick : true,
upscale : false
}))
.pipe(gulp.dest('./images/tmb_menu'));
});
Use gulp-rename:
var rename = require("gulp-rename");
then add to your pipe:
gulp.task('rsz_tmb_menu',function(){
var index = 0;
gulp.src('your_glob')
.pipe(your processing func)
.pipe(rename(function (path) {
path.basename += ("-" + index++);
}))
.pipe(...dst...)
I'd like to do it to append the size of the original imageā€¦
In my case, this is a requirement of photoswipe.
Trying to do it with gulp, unfortunately, I get stuck even when trying to append the size of the current image:
var sizeOf = require('image-size');
(...)
.pipe(rename(function (path) {
var dimensions = sizeOf(path);
path.basename += ("-" + dimensions.width + "x" + dimensions.height);
}))
raises an error:
node_modules/image-size/lib/index.js:79
throw new TypeError('invalid invocation');
answering my own question in case it helps someone
based on http://www.pixeldonor.com/2014/feb/20/writing-tasks-gulpjs/
return gulp.src(...)
.pipe(through.obj(function (chunk, enc, cb) {
dimensions = sizeOf(chunk.path);
extname = Path.extname(chunk.path);
dirname = Path.dirname(chunk.path);
basename = Path.basename(chunk.path, extname);
chunk.path = Path.join(dirname, basename + "-" + dimensions.width + "x" + dimensions.height + extname);
this.push(chunk);
cb(null, chunk);
}))
.pipe(imageResize({
width : 600,
height : 600,
crop : true,
upscale : true
}))
.pipe(gulp.dest(...));

multiple async mongo request generate messed up returns

I'm trying to build a JSON out of multiple requests on my mongodb.
since I'm not using DBRef, I have to build the "table joints" by myself, and that's how I ended up in this mess.
This is the code that is giving me the headaches from a couple of days now.
(the mongo part is done with mongoskin)
var getUserFeed = function(thelimit, out) {
userfeed = db.collection("userfeed");
apparel = db.collection("apparel");
store = db.collection("stores");
if(thelimit)
args = {limit:thelimit, sort: [['date',-1]]};
userfeed.find({},args).toArray(function(e, feed) {
if (e) console.log("error: ", e);
// gather aparel infos
var i=0;
var ret_feeds = [];
feed.forEach(function(cur_feed) {
var outfits=[];
console.log("beginning with: " + cur_feed.url);
var resfeed = "";
resfeed = cur_feed;
resfeed.url = baseurl + snapurl + resfeed.url + "_small.jpg";
i=0;
cur_feed.apparel_ids.forEach(function(item) {
/*>>*/ apparel.find({"_id": item},{limit:1}).toArray(function(e, results) {
console.log(">>>>>>>>>>> APPAREL_FIND { i:" + i + "}");
if (e) console.log("error: ", e);
results = results[0];
if(results.apparel_cat == 1)
url_subcat = "pants/";
else if(results.apparel_cat == 2)
url_subcat = "shirts/";
else if(results.apparel_cat == 2)
url_subcat = "tshirts/";
results.thumb = baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg";
results.size = "M"; ///// TOBE REAL VERY SOON
results.gallery = [
baseurl + outfiturl + url_subcat + results.apparel_id + "/model.jpg",
baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg"
];
outfits.push(results); // quick and dirty, 2 b refined..
i++;
if(i>=cur_feed.apparel_ids.length)
{
// pack it up
// resfeed.url = resfeed.url;
resfeed.outfits = outfits;
resfeed.fav = false;
resfeed.bough = false;
// retrieve store infos
/*>>>*/ store.find({"_id":resfeed.store_id}, {limit: 1}).toArray(function(e, resstore) {
console.log("\t############# STORE_FIND { i:" + i + "}");
if (e) console.log("error: ", e);
resfeed.store = resstore[0];
resfeed.store.class = "hem";
ret_feeds.push(resfeed);
if(ret_feeds.length >= feed.length)
{
console.log("\t\t######################calling return [ ret_feeds.length = " + ret_feeds.length + " feed.length = " + feed.length);
out.send(ret_feeds);
}
});
}
});
});
});
});
}
This code fails, because returns the json before finishing its task, so the next time that it tries to return another json it crashes miserably due to the fact the the headers have already been sent.
Now as you can see, I have 3 collections: userfeed, apparel and stores.
the goal of this function is to retrieve all the items in the userfeed collection, extract the outfits (based on the outfit_id array that is part of the userfeed collection), and also extract the store infos related in the same way to each userfeed entry, like so:
I know that async.js or equivalent is the way to go: I've red like a gazillion of other posts here on SO, but I still can't get my head around it, probably because the whole mechanism behind the async.js or flow control in general it's still out of focus in my mind.
I'm still a noob at node :)
UPDATE
I think I found the right path for understanding here: http://www.sebastianseilund.com/nodejs-async-in-practice
this guy made a terrific job in describing use-case by use-case all the ways to apply async.js to your code.
I'll post the solution as soon as I get around it.
UPDATE 2
Thanks to the above dude I could work out a working solution, below is the answer.
After so much struggling I have finally managed to get a solution.
async.js was the answer as I was (obviously) suspecting.
FYI here's the working code.
If you like to point out improvements or anything else, you are more than welcome
var getUserFeed = function(thelimit, out) {
userfeed = db.collection("userfeed");
apparel = db.collection("apparel");
store = db.collection("stores");
var args;
if(thelimit)
args = {limit:thelimit, sort: [['date',-1]]};
var outfits=[];
var feeds = array();
async.series([
// userfeed find
function(callback) {
userfeed.find({},args).toArray(function(e, feed) {
if(e) callback(e);
feeds = array(feed);
console.log(feeds.length + " retrieved. stepping in");
callback(null, null);
});
},
// join
function(callback) {
async.forEach(feeds, function(thefeed, callback) {
var i = feeds.indexOf(thefeed);
async.parallel([
// load apparel infos
function(callback) {
console.log("\t >>> analyzing thefeed id " + thefeed._id);
async.forEach(thefeed.apparel_ids, function(apparel_id, callback) {
apparel.find({"_id": apparel_id},{limit:1}).toArray(function(e, results) {
if (e) console.log("error: ", e);
results = results[0];
if(results.apparel_cat == 1)
url_subcat = "pants/";
else if(results.apparel_cat == 2)
url_subcat = "shirts/";
else if(results.apparel_cat == 2)
url_subcat = "tshirts/";
results.thumb = baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg";
results.size = "M"; ///// TOBE REAL VERY SOON
results.gallery = [
baseurl + outfiturl + url_subcat + results.apparel_id + "/model.jpg",
baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg"
];
console.log("\t\t### pushing data into thefeed_index: " + i);
if(!util.isArray(feeds[i].oufits)) feeds[i].outfits = array();
feeds[i].outfits.push(results);
callback(null, null);
});
}, callback);
},
// load store infos
function(callback) {
store.find({"_id":thefeed.store_id}, {limit: 1}).toArray(function(e, resstore) {
console.log("\t### STORE_FIND");
if (e) console.log("error: ", e);
feeds[i].store = resstore[0];
feeds[i].store.class = "hem";
callback(null, null);
});
}
], callback);
}, callback);
}
// MAIN
], function(err, result) {
console.log("feed retrieval completed. stepping out");
if (err) return next(err);
out.send(feeds);
});
};

Can't get d3 tooltips to display name, only number

I'm trying to set up d3 tooltips for my choropleth map. I have the tooltips working and am able to pull in the data from lotteryMapNum.json that I am using to assign the map colors, but I can't get any text to show up. I'm either getting "undefined" or NaN no matter what I do. I'm thinking that I need to call the data some other way, but I'm not sure how.
var newDict = {};
d3.json("data/myData/lotteryMapNum.json", function(data) {
data.forEach(function(d) { newDict[d.id] = +d.hopeDollars;})
data.forEach(function(d) { newDict[d.COUNTY] = +d.COUNTY;});
});
d3.json("data/myData/simpleGA.json", function(json) {
counties.selectAll("path")
.data(json.features)
.enter().append("svg:path")
.attr("class", function(d) { return quantize(newDict[d.id]);})
.attr("d", path)
.call(d3.helper.tooltip()
//.attr({class: function(d, i) { return d + ' ' + i + ' A'; }})
.text(function(d){ return 'value: '+newDict[d.id]+newDict[d.COUNTY]; })
)
.on('mouseover', function(d){ d3.select(this).style({fill: 'green', stroke: 'red'}); })
.on('mouseout', function(d){ d3.select(this).style({fill: '', stroke: ''}); });
});
lotteryMapNum.json
[
{"COUNTY":"APPLING",
"hopeDollars":12921240,
"id":"0"}
]
simpleGA.json
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 0, "properties": { "NAMELSAD10": "Appling County"}, "geometry": { "type": "Polygon", "coordinates": [ [ [ -83.04292, 30.947296 ], [ -83.05332, 30.94753 ],] ] } }
]
}
pretty simple fix i think. try getting rid of the + in front of d.COUNTY and then just making the value of each id key an array:
d3.json("data/myData/lotteryMapNum.json", function(data) {
data.forEach(function(d) { newDict[d.id] = [+d.hopeDollars, d.COUNTY];});
then just access the desires tooltip text in the same way:
.text(function(d){ return 'value: '+ newDict[d.id][0] + newDict[d.id][1]; })
after a quick look, I see two potential problems...
1) It doesn't look like there is any property 'd.COUNTY' in json.features... 'd' in the text() function is referencing the data on the dom element, which comes directly from simpleGA.json ('id' is present, but 'COUNTY' is not.)
Instead, you're probably wanting to reference:
d.properties['NAMELSAD10']
2) The two requests are being loaded in parallel, but one response depends on the other. It is possible that the completion code for simpleGA.json is executing before the lotteryMapNum.json request is complete... in which case the elements of 'newDict' may still be undefined.
A possible solution for this would be to chain the two requests based on the dependency:
var newDict = {};
d3.json("data/myData/lotteryMapNum.json", function(data) {
data.forEach(function(d) { newDict[d.id] = +d.hopeDollars;})
data.forEach(function(d) { newDict[d.COUNTY] = +d.COUNTY;});
d3.json("data/myData/simpleGA.json", function(json) {
counties.selectAll("path")
.data(json.features)
.enter().append("svg:path")
.attr("class", function(d) { return quantize(newDict[d.id]);})
.attr("d", path)
.call(d3.helper.tooltip()
//.attr({class: function(d, i) { return d + ' ' + i + ' A'; }})
.text(function(d){ return 'value: '+newDict[d.id]+newDict[d.COUNTY]; })
)
.on('mouseover', function(d){ d3.select(this).style({fill: 'green', stroke: 'red'}); })
.on('mouseout', function(d){ d3.select(this).style({fill: '', stroke: ''}); });
});
});
If you need more help, post a link to the two .json files and I can better troubleshoot with a jsfiddle.
EDIT: jsfiddle is giving me a lot of woes with large filesizes... but here's basically what I meant with a database-like join... also was working with a list instead of a geojson map:
// load json1
d3.json("lotterMapNum.json", function(lotterMapNum) {
// load json2
d3.json("simpleGA.json", function(simpleGA) {
for (var i = 0; i < simpleGA.features.length; i++) {
newDict[simpleGA.features[i].id] = simpleGA.features[i];
}
for (var j = 0; j < lotteryMapNum.length; j++) {
if (typeof newDict[lotteryMapNum[j].id] === 'undefined') {
newDict[lotteryMapNum[j].id] = {};
}
newDict[lotteryMapNum[j].id].hopeDollars = lotteryMapNum[j].hopeDollars;
newDict[lotteryMapNum[j].id].COUNTY = lotteryMapNum[j].COUNTY;
}
var counties = d3.select('#counties')
.data(newDict)
.enter()
.append('li')
.text(function(d){return d.id + ': ' + d.COUNTY + ', $' + d.hopeDollars;});
});
});