d3js update with div and attributes - d3.js

I am struggling with an update on d3js. The code below if uncommented does not update the values - actually, the initial layout disappears.
I cannot figure out yet. I just want the price to be updated.
Any help would be greatly appreciated.
updated
Someone (anonymous) updated http://jsfiddle.net/1qqdc0ns/11/ - so I am looking into this one.
I have cleaned my original version into http://jsfiddle.net/1qqdc0ns/12/ which still does not update properly.
function update(data) {
console.log("update " + data);
var quotes = d3.select("#quoteContainer")
.selectAll("div")
.data(data);
// ENTER
var q = quotes.enter()
.append("div")
.attr("class", "quotePanel");
var m = q.append("div").attr("class", "carmodel");
var p = q.append("div").attr("class", "price");
var f1 = p.append("span").attr("class", "bigFigure");
var f2 = p.append("span").attr("class", "smallFigure");
f1.text(function (d) {
var priceStr = d["price"].toString();
var bigFigure = priceStr.substr(0, 4);
console.log("bigFigure: " + bigFigure);
return bigFigure;
});
f2.text(function (d) {
return "00";
});
quotes.select("div").text(function (d) {
var modelname = d["symbol"];
console.log("Setting model name " + modelname);
return modelname;
});
//quotes.exit().remove();
}
function onclick() {
update([{
"symbol": "BMW",
"price": 9000
}, {
"symbol": "PIGEOT",
"price": 124
}]);
};
d3.select('#update')
.on("click", onclick);
update([{
"symbol": "ASTON",
"price": 3000
}, {
"symbol": "MARTIN",
"price": 6500
}]);

Thanks Igor.
The solution is:
http://jsfiddle.net/Ihorad/1qqdc0ns/14/
function update(data) {
var quotes = d3.select("#quoteContainer")
.selectAll(".quotePanel")
.data(data);
var q = quotes.enter()
.append("div")
.attr("class", "quotePanel");
var m = q.append("div").attr("class", "carmodel");
var p = q.append("div").attr("class", "price");
var f1 = p.append("span").attr("class", "bigFigure");
var f2 = p.append("span").attr("class", "smallFigure");
quotes.select(".carmodel").text(function (d) {
var modelname = d["symbol"];
return modelname;
});
quotes.select(".bigFigure").text(function (d) {
var priceStr = d["price"].toString();
var bigFigure = priceStr.substr(0, 4);
return bigFigure;
});
quotes.select(".smallFigure").text(function (d) {
return "00";
});
quotes.exit().remove();
}

Related

How add crossfilter function filterRange. Dc.js on server side

I moved calculations to the server side, because data is large.
I want to draw dc.seriesChart, but after specifying date range
.x(d3.scaleTime().domain([minDate, maxDate]))
an error appears on page load:
Uncaught TypeError: t.filterRange is not a function.
Please help implement filterRange in script.js, app.js and script.js code is attached.
I'm using the crossfilter express pattern from lastlegion/dc.js-server-side-crossfilter.
app.js
var express = require('express');
var path = require('path');
var anyToJSON = require('anytojson');
var crossfilter = require('crossfilter');
var d3 = require('d3');
var app = express();
app.listen(3000, () => console.log('Listening at Port 3000'));
app.use(express.static('public'));
var dimensions = {};
var groups = {};
app.get("/data", function(req, res, next) {
var results = {};
filter = req.param("filter") ? JSON.parse(req.param("filter")) : {}
console.log(filter);
for (dimension in dimensions) {
console.log("Dimension: " + dimension);
if (filter[dimension]) {
if ((filter[dimension]).length > 1) {
console.log(filter);
dimensions[dimension].filterFunction(function (d) {
var filters = (filter[dimension]);
for (var i = 1; i < filters.length; i++) {
var filter = filters[i];
if (filter.isFiltered && filter.isFiltered(d)) {
return true;
} else if (filter <= d && filter >= d) {
return true;
}
}
return false;
});
}
else {
console.log(filter[dimension]);
dimensions[dimension].filter(filter_value(filter[dimension]));
}
} else {
dimensions[dimension].filterAll()
}
}
for (dimension in groups) {
console.log("Group: " + dimension);
var group = groups[dimension];
results[dimension] = {
values: group.all(),
top: group.top(1)[0].value
};
}
res.writeHead(200, {
'content-type': 'application/json'
});
res.end((JSON.stringify(results)));
});
function filter_value(dir) {
console.log( "dfsk");
if ((dir).length === 1) {
return dir[0];
} else if ((dir).length === 0) {
return null
}
}
anyToJSON.csv({ path: "input/dataset.csv"}, function(records) {
var dateFormat = d3.timeFormat("%Y-%m-%d %H:%M:%S");
var dateFormatParser = d3.timeParse("%Y-%m-%d %H:%M:%S");
records.forEach(function(d) {
d.timestamp = dateFormat(dateFormatParser(d.timestamp));
});
var ndx = crossfilter(records);
var all = ndx.groupAll();
console.log("...");
var dateDim = ndx.dimension(function (d) { return d.timestamp; });
var datePriorityDim = ndx.dimension(function (d) { return [d.priority, d.timestamp];});
var numRecordsByDate = dateDim.group();
var datePriorityGroup = datePriorityDim.group().reduceCount();
dimensions.dateDim = dateDim;
dimensions.datePriorityDim = datePriorityDim;
groups.numRecordsByDate = numRecordsByDate;
groups.datePriorityGroup = datePriorityGroup;
});
module.exports = app;
public/js/script.js
var priorityTimeChart = new dc.seriesChart("#priority-timeline");
var filteredData = {};
var queryFilter = {};
window.filter = function(filters) {
filters.forEach(function(d, i) { charts[i].filter(d); });
renderAll();
};
window.reset = function(i) {
charts[i].filter(null);
renderAll();
};
var refresh = function(queryFilter){
d3.json("/data?filter="+JSON.stringify(queryFilter), function(d){
filteredData = d;
dc.redrawAll();
});
};
var dateDim = {
filter:function(f){
if(f){
queryFilter["dateDim"]=f;
refresh(queryFilter);
}
},
filterAll:function(){
},
filterRange:function(){
}
};
var numRecordsByDate = {
all:function(){
return filteredData["numRecordsByDate"].values;
},
order: function(){
},
top:function(){
}
};
var datePriorityDim = {
filter:function(f){
if(f){
queryFilter["datePriorityDim"]=f;
refresh(queryFilter);
}
},
filterAll:function(){
}
};
var datePriorityGroup = {
all:function(){
return filteredData["datePriorityGroup"].values;
},
order: function(){
},
top:function(){
}
};
var minDate = "Fri Mar 13 2020 11:49:35 GMT+0300";
var maxDate = "Tue Mar 17 2020 14:50:03 GMT+0300";
priorityTimeChart
.height(h_row2)
.chart(function(c) { return new dc.lineChart(c).curve(d3.curveCardinal).evadeDomainFilter(true); })
.x(d3.scaleTime().domain([minDate, maxDate]))
.brushOn(true)
.yAxisLabel("Events")
.xAxisLabel("Date")
.elasticY(true)
.dimension(dateDim)
.group(datePriorityGroup)
.mouseZoomable(true)
.colors(d3.scaleOrdinal().domain(['High','Middle','Low']).range(["red", "green", "yellow"]))
.seriesAccessor(function(d) {return "priority: " + d.key[0];})
.keyAccessor(function(d) {return +d.key[1];})
.valueAccessor(function(d) {return +d.value;})
.legend(dc.legend().x(350).y(350).itemHeight(13).gap(5).horizontal(1).legendWidth(140).itemWidth(70))
.yAxis().ticks(3);
priorityTimeChart.yAxis().tickFormat(function(d) {return d3.format(',d')(d);});
priorityTimeChart.margins().left += 10;
priorityTimeChart.filterHandler(function(dimension, filters){
if(filters)
dimension.filter(filters);
else
dimension.filter(null);
return filters;
});
function init(){
d3.json("/data?filter={}", function(d){
filteredData = d;
dc.renderAll();
});
};
init();

aspx page on Sharepoint error: Function Expected only when rendering datatable

I have been building a reporting page in sharepoint with dc and crossfilter.
Right now on my page, I have 5 pie charts that render with no problem. However, when I tried to add a dc datatable to the page to show results of the charts as they are filtered, I get a javascript error on "resultsChart.render();"
Because no errors are given when I render each of the pie charts, I assume this to mean that something is off with the datatable object, or that I cannot call render() on that object (whatever it thinks it is).
Here are the relevant pieces of my code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js" type="text/javascript">
<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.19/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js" type="text/javascript">
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.0.6/dc.min.js" type="text/javascript">
//connect to sharepoint site (change this URL to redirect)
var siteUrl = 'path';
var masterData = [];
//retrieve list data from above sharepoint site based on List Name
function retrieveListItems() {
var clientContext = new SP.ClientContext(siteUrl);
var oList = clientContext.get_web().get_lists().getByTitle('Upcoming');
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml("<View><Query></Query></View>");
this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, this.onQuerySucceeded),
Function.createDelegate(this, this.onQueryFailed)
);
}
//on query success
function onQuerySucceeded(sender, args) {
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext()) {
var data = {};
var oListItem = listItemEnumerator.get_current();
//set field keys on array objects
data.project = oListItem.get_item('Project_x0020_Type');
data.stoplight = oListItem.get_item('Stoplight');
data.appmgr = oListItem.get_item('AIT_x0020_App_x0020_Manager');
data.compdate = oListItem.get_item('Completion_x0020_Due_x0020_Date');
data.ait = oListItem.get_item('AIT_x0020_Number');
data.lob = oListItem.get_item('Business_x0020_Area');
data.sublob = oListItem.get_item('Business_x0020_Sub_x0020_Area');
masterData.push(data);
}//end while
var projectChart = dc.pieChart("#project", "project");
var stoplightChart = dc.pieChart("#stoplight", "stoplight");
var appmgrChart = dc.pieChart("#appmgr", "appmgr");
var lobChart = dc.pieChart("#lob", "lob");
var sublobChart = dc.pieChart("#sublob", "sublob");
var resultChart = dc.dataTable("#result_table", "result");
var ndx = crossfilter(masterData),
projectType = ndx.dimension(function(d) { return d.project;}),
stoplight = ndx.dimension(function(d) { return d.stoplight;}),
appMgr = ndx.dimension(function(d) { return d.appmgr;}),
compdate = ndx.dimension(function(d) { return d.compdate;}),
lob = ndx.dimension(function(d) { return d.lob;}),
sublob = ndx.dimension(function(d) { return d.sublob;})
projectTypeGroup = projectType.group();
stoplightGroup = stoplight.group(),
appMgrGroup = appMgr.group(),
compDateGroup = compdate.group(),
lobGroup = lob.group(),
sublobGroup = sublob.group();
projectChart
.dimension(projectType)
.group(projectTypeGroup)
.width(200)
.height(200)
.innerRadius(75)
stoplightChart
.dimension(stoplight)
.group(stoplightGroup)
.width(200)
.height(200)
.innerRadius(75)
appmgrChart
.dimension(appMgr)
.group(appMgrGroup)
.width(200)
.height(200)
.innerRadius(75)
lobChart
.dimension(lob)
.group(lobGroup)
.width(300)
.height(300)
.innerRadius(117)
sublobChart
.dimension(sublob)
.group(sublobGroup)
.width(200)
.height(200)
.innerRadius(75)
resultChart
.dimension(compdate)
.group(compDateGroup)
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
projectChart.render();
stoplightChart.render();
appmgrChart.render();
lobChart.render();
sublobChart.render();
resultChart.render();
}
function onQueryFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
SP.SOD.executeOrDelayUntilScriptLoaded(retrieveListItems, 'sp.js');
</script>
Any and all help is extremely appreciated!
I figured it out. According to the dc.dataTable documentation, you cannot use a crossfilter group as the .group attribute on a datatable. Instead, you must explicitly use a function there.
So it should be
resultChart
.dimension(compdate)
.group(function(d) { return d.compdate;})
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);
Instead of
resultChart
.dimension(compdate)
.group(compDateGroup)
.columns([
function(d) { return d.ait},
function(d) { return d.project},
function(d) { return d.stoplight},
function(d) { return d.compdate}
])
.size(100);

Dynamic filtering with D3

I'm quite new to D3 and coding in general. I'm trying to set up a bar chart which includes/excludes data depending on a checkbox. I have a set of product groups and countries which I want to toggle in/out of the total represented by the bar. The output should be one bar per product.
My full data set has many more products, product groups and countries so it is not viable to create a key-value pair for each potential combination of checkboxes. Instead I would like to create a function that re-evaluates the checkboxes and re-filters the data and updates the rollup when a checkbox is changed.
I'm not sure where this function should sit in my code or what it should look like... This is what I'm working with at the moment:
var data = data.filter(function(d) {
if (document.getElementById("nz_button").checked) {
return d.country == 'NZ'
}
if (document.getElementById("au_button").checked) {
return d.country == 'AU'
}
if (document.getElementById("us_button").checked) {
return d.country == 'US'
}
})
// to see how many distinct groups there are and sum volume
var products = d3.nest()
.key(function(d) {
return d.product
})
.rollup(function(leaves) {
var sum = 0;
leaves.forEach(function(d) {
sum += d.volume;
})
return sum
})
.entries(data);
Full code: http://plnkr.co/edit/qezdwMLt48RPc8KH17hS?p=preview
Maybe I should be working with selections and re-running the nest/rollup when required?
Any help appreciated. Thanks :)
You can move the full code which makes the graph in a new function like this:
function makeDataGraph(data) {//function to make the graph.
//
// FILTER
//
var data = data.filter(function(d) {
if (document.getElementById("au_button").checked) {
return d.country == 'AU'
}
if (document.getElementById("us_button").checked) {
return d.country == 'US'
}
if (document.getElementById("nz_button").checked) {
return d.country == 'NZ'
}
})
// to see how many distinct groups there are and sum volume
var products = d3.nest()
.key(function(d) {
return d.product
})
.rollup(function(leaves) {
var sum = 0;
leaves.forEach(function(d) {
sum += d.volume;
})
return sum
})
.entries(data);
// sorting on descending total
console.log(products);
products.sort(function(a, b) {
return b.values - a.values
})
var max = d3.max(products, function(d) {
return d.values;
});
var xscale = d3.scale.linear()
.domain([0, max])
.range([0, 600])
var svg = d3.select("svg");
//
// Still needs to be cleaned up \/ \/
//
var rects = svg.selectAll("rect.product")
.data(products)
rects.exit().remove();
rects.enter().append("rect").classed("product", true)
rects.attr({
x: 200,
y: function(d, i) {
return 100 + i * 50
},
width: function(d, i) {
return xscale(d.values)
},
height: 50
}).on("click", function(d, i) {
console.log(i, d);
})
var labels = svg.selectAll("text.label")
.data(products)
labels.exit().remove();
labels.enter().append("text").classed("label", true)
labels.attr({
x: 195,
y: function(d, i) {
return 128 + i * 50
},
"text-anchor": "end",
"alignment-baseline": "middle"
}).text(function(d) {
return d.key || "N/A"
})
var volume = svg.selectAll("text.volume")
.data(products);
volume.exit().remove();
volume.enter().append("text").classed("volume", true)
volume.attr({
x: function(d, i) {
return 205 + xscale(d.values)
},
y: function(d, i) {
return 128 + i * 50
},
"text-anchor": "start",
"alignment-baseline": "middle"
}).text(function(d) {
return d.values || "N/A"
})
}
Remember to do rects.exit().remove(); so that when the data is changed on click of the checkbox, rectangles related to old data is removed.
Now you can call this function from the click event and also afterloading the tsv like this:
d3.tsv("data.tsv", function(err, udata) {
var udata = udata.map(process);
console.log("udata", udata);
var data = udata // making new var to preserve unfiltered data
makeDataGraph(data);//call the function to make graph
function handleClick() { // event handler...
makeDataGraph(data)
}
//add listener to all check boxes.
d3.selectAll(".filter_button").on("click", handleClick);
});
working code here

dc.js time series line chart

I'm plotting a time series line chart which takes time on x-axis, other resp values on y-axis. I need help doing that. The graph is not plotted with the following code. It shows only the lines(axes) and labels but no graph nor the values on axes.
data = [{"date": "31-03-2015 11:05:36", "snr": 3.2}, {"date": "31-03-2015 11:05:36", "snr": 4.9},...{"date": "31-03-2015 11:05:36", snr: 5.0}];
var parseDate = d3.time.format("%d-%m-%Y %H:%M:%S").parse;
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var dimByTime = cf.dimension(function(d) { return d.date;});
var groupForSNR = dimByTime.groupAll();
groupForSNR.all = function() { return 1; };
var snrAP = dc.lineChart("#snrAP");
snrAP
.dimension(dimByTime)
.group(groupForSNR).valueAccessor(function(d) { return d.value.snr; })
.x(d3.time.scale())
.xUnits(d3.time.day);
dc.renderAll();
I want to show snr on y-axis and the corresponding time on x-axis.
Well, I've fixed it myself. May not be the optimum solution, but it still serves my purpose. Here is the code with modification...
var dimByTime = cf.dimension(function(d) { return d3.time.minute(d.date);});
var groupForSNR = dimByTime.group().reduce(
function reduceAdd(p, v) { return v.snr; },
function reduceRemove(p, v) { return v.snr; },
function reduceInitial() { return 0; });
groupForSNR.dispose();
var snrAP = dc.lineChart("#snrAP");
snrAP
.dimension(dimByTime)
.group(groupForSNR)
.x(d3.time.scale().domain([new Date(2015, 02, 21), new Date()]))
.xUnits(d3.time.minute)
.title(function(d){ return "Time: " + d.data.key.toDateString() + "\nS/N Ratio: " + numFormat(d.data.value); })
.xAxis().ticks(5);

Heatmap for multivalue variables

I am creating a heatmap using dc.js master API. My example is based on this. But the major difference is that the rows and columns have multivalues.
Below is the dataset in CSV format:
id,city,visitor,animals,food
1,NYC,854,"Lion,Tiger,Rabbit,Ape,Zebra,Monkey,Elephant,Horse","Apple,Banana,Chicken,Egg,Fish,Grape,Ham,Ice,Juice"
2,LAX,123,"Cat,Tiger,Rabbit,Ape,Whale,Bear,Zebra,Donkey,Goat,Turtle","Apple,Banana,Cake,Fish,Sugar,Bamboo,Leaf,Ham,Ice,Water"
3,LON,584,"Lion,Tiger,Ape,Shark,Panda,Zebra,Deer,Turtle,Bear,","Apple,Coke,Cake,Fish,Bamboo,Water,Grape,Orange"
4,TOR,704,"Cat,Rabbit,Ape,Whale,Bear,Panda,Donkey,Turtle,Cheetah","Banana,Cake,Orange,Kiwi,Sugar,Bamboo,Leaf,Goat,Ice,Juice"
5,SFO,855,"Lion,Tiger,Ape,Zebra,Monkey,Elephant,Donkey,Goat,Turtle,Cheetah","Apple,Cake,Grape,Ham,Juice,Hay"
6,DAL,654,"Salmon,Penguin,Rabbit,Ape,Whale,Goat,Tortoise","Apple,Banana,Cake,Ice,Water,Earthworm"
Animals and food each has multiple values. The rows are based on food and the columns are based on animals. I am able to create the heatmap but there is no interaction with other charts. For example, if I can click on a box, there is no action in the bar chart. The error message found at console is "Uncaught TypeError: Cannot read property 'all' of undefined." Here is the code.
parsecsv = function (string) {
var rows = d3.csv.parse(string);
var records = [];
rows.forEach(function (d, i) {
d.animals= d.animals.split(/,/);
d.food = d.food.split(/,/);
records.push(d)
});
return records
};
var chartGroup = "chartGroup";
var heatmapChart = dc.heatMap("#heatmap", chartGroup);
var pieChart1 = dc.pieChart("#piechart1", chartGroup);
var pieChart2 = dc.pieChart("#piechart2", chartGroup);
var barChart = dc.barChart("#barchart", chartGroup);
var ndx = crossfilter(parsecsv(csvtext));
console.log(ndx);
var animals_food_dim = ndx.dimension(function(d) { console.log (d.animals);return [d.animals, d.food]; });
var animals_food_group = animals_food_dim.groupAll().reduce(reduceAdd, reduceRemove, reduceInitial).value();
function reduceAdd(p, v) {
// skip empty values
if (v.animals[0] === "" || v.food[0]==="") return p;
v.animals.forEach (function(val1, idx1) {
v.food.forEach (function(val2, idx2) {
var temp_array=[val1,val2];
p[temp_array] = (p[temp_array] || 0) + 1; //increment counts
//console.log(val1+":"+val2, p[temp_array],temp_array);
});
});
return p;
}
function reduceRemove(p, v) {
if (v.animals[0] === "") return p; // skip empty values
v.animals.forEach (function(val1, idx1) {
v.food.forEach (function(val2, idx2) {
var temp_array=[val1,val2];
p[temp_array] = (p[temp_array] || 0) - 1; //increment counts
//console.log(val1+":"+val2,p[temp_array]);
});
});
return p;
}
function reduceInitial() {
return {};
}
animals_food_group.all = function() {
var newObject = [];
for (var key in this) {
if (this.hasOwnProperty(key) && key != "all" && key != "top") {
var temp_array=[key.substring(0,key.indexOf(",")),key.substring(key.indexOf(",")+1)];
//console.log(temp_array,this[temp_array]);
newObject.push({
key: temp_array,
value: this[temp_array]
});
}
}
return newObject;
};
animals_food_group.top = function(count) {
var newObject = this.all();
newObject.sort(function(a, b){return b.value - a.value});
return newObject.slice(0, count);
};
heatmapChart
.width(12 * 80 + 80)
.height(27 * 10 + 40)
.dimension(animals_food_dim)
.group(animals_food_group)
.keyAccessor(function(d) {return d.key[0];})
.valueAccessor(function(d) {return d.key[1];})
.colorAccessor(function(d) {return +d.value;})
.linearColors(["#FFEAEA", "#FF0000"])
.title(function(d) {
return "Animals: " + d.key[0] + "\n" +
"Food: " + d.key[1] + "\n" +
"Count: " + ( d.value) + " ";})
.calculateColorDomain();
heatmapChart.render();
var city_dim = ndx.dimension(function(d) {return d.city; });
var city_group = city_dim.group().reduceSum(function(d) {return +d.visitor;});
barChart
.dimension(city_dim)
.group(city_group)
.width(12 * 80 + 80)
.height(480)
.elasticY(true)
.x(d3.scale.ordinal().domain(["NYC","LAX","LON","TOR","SFO","DAL"]))
.xUnits(dc.units.ordinal)
.elasticY(true)
.centerBar(false)
.xAxisPadding(50);
barChart.render();
I bet I must did something wrong in reduceAdd, reduceRemove and/or animals_food_group.all. Would someone help me how to fix the interaction problem?
The JSFiddle is http://jsfiddle.net/chitester11/u33yb8k5/.

Resources