Need to make custom reducer to deduct two values - dc.js

"Date(UTC)","Market","Type","Price","Amount","Total","Fee","Fee Coin"
12:18:07","ETCBTC","BUY","0.002064","1.05","0.00216720","0.00105","ETC"
"2018-05-26 06:01:12","ETCBTC","SELL","0.00207","5.86","0.01213020","0.00001213","BTC"
"2018-05-25 22:47:14","ETCBTC","BUY","0.002","1.32","0.00264000","0.00132","ETC"
This is part of my dataset. The problem is that in my data set "Total" that I need to use is just numbers - to make them work I have to connect them with "Type" (BUY/SELL).
BUY should work like "-" and SELL like "+"; the difference between them is that what I need to display.
I am just learning. So I didn't try a lot.
function show_profit(ndx) {
var typeDim = ndx.dimension(dc.pluck("Type"));
var profit = typeDim.group().reduce(
function (p, v) {
p.count++;
p.total += v.Total;
return p;
},
function (p, v) {
p.count--;
p.total -= v.Total;
return p;
},
function () {
return { count:0, total: 0};
}
);
dc.barChart("#profit")
.width(500)
.height(300)
.dimension(typeDim)
.group(profit)
.valueAccessor(function (d) {
if (d.value.count == 0) {
return 0;
} else {
return d.value.total;
}
})
.transitionDuration(500)
.x(d3.scale.ordinal())
.xUnits(dc.units.ordinal)
.elasticY(true)
.xAxisLabel("Type")
.yAxisLabel("Amount")
.yAxis().ticks(20);
}
I just made graph with volume per BUY and SELL.
My target is to find the difference between BUY and SELL and display it in a line graph.

I'm not sure if I understand your question completely, but if you simply want to apply SELL as positive and BUY as negative, you should be able to multiply the values by 1 or -1 in your reduction functions:
function mult(type) {
switch(type) {
case 'SELL': return 1;
case 'BUY': return -1;
default: throw new Error('unknown Type ' + type);
}
var profit = typeDim.group().reduce(
function (p, v) {
p.count++;
p.total += mult(v.Type) * v.Total;
return p;
},
function (p, v) {
p.count--;
p.total -= mult(v.Type) * v.Total;
return p;
},
function () {
return { count:0, total: 0};
}
);

Related

Display multiple bar on barChart from a custom reducer

I have a group with custom reducer calculating various total and average values. The goal is to show them all on the same barChart. But I can only get the first bar to show. Here is the JSFiddler
https://jsfiddle.net/71k0guxe/15/
Is it possible to show all the value on the barChart?
Thanks in advance!
Data
ID,SurveySent,ResponseReceived
1,Yes,No
2,No,No
3,Yes,Yes
4,No,No
5,Yes,Yes
6,No,No
7,Yes,No
8,No,No
9,Yes,No
10,No,No
Code
var chart = dc.barChart("#test");
//d3.csv("morley.csv", function(error, experiments) {
var experiments = d3.csvParse(d3.select('pre#data').text());
var ndx = crossfilter(experiments),
dimStat = ndx.dimension(function(d) {return "Statistics";}),
groupStat = dimStat.group().reduce(reduceAdd, reduceRemove, reduceInitial);
function reduceAdd(p, v) {
++p.count;
if (v.SurveySent === "Yes") p.sent++;
if (v.ResponseReceived === "Yes") p.received++;
return p;
}
function reduceRemove(p, v) {
--p.count;
if (v.SurveySent === "Yes") p.sent--;
if (v.ResponseReceived === "Yes") p.received--;
return p;
}
function reduceInitial() {
return {count: 0, sent: 0, received: 0};
}
chart
.width(400)
.height(400)
.xUnits(dc.units.ordinal)
.label(function(d) { return d.data.value })
.elasticY(true)
.x(d3.scaleOrdinal().domain(["Total", "Sent", "Received"]))
.brushOn(false)
.yAxisLabel("This is the Y Axis!")
.dimension(dimStat)
.group(groupStat)
.valueAccessor(function (d) {
//Is it possible to return count sent and received all from here?
return d.value.count;
})
.on('renderlet', function(chart) {
chart.selectAll('rect').on("click", function(d) {
console.log("click!", d);
});
});
chart.render();
Just got some idea from the FAQ section of dc.js/wiki/FAQ
Fake Groups
"dc.js uses a very limited part of the crossfilter API - in fact, it really only uses dimension.filter() and group.all()."
I don't care about filtering, so i just need to mark up my own group.all. Basically transpose it from one row to multiple row. Works my purpose.
/* solution */
var groupStatTranposed = group_transpose(groupStat);
function group_transpose(source_group, f) {
return {
all:function () {
return [
{key: "Total", value: source_group.all()[0].value.count},
{key: "Sent", value: source_group.all()[0].value.sent},
{key: "Received", value: source_group.all()[0].value.received}
];
}
};
}
//use groupStatTranposed in the chart.
/** solution */

dc.js bubble chart - multidimension grouping issue and unable to get custom reducer to work

I'm currently trying to produce a dashboard in dc.js for my master's thesis and I have hit a real roadblock today if anyone could please help it would be much appreciated. I'm new to Javascript and dc so I'll try my best to explain...
My data format (Probe Request with visible SSID):
{"vendor":"Huawei Technologies Co.Ltd","SSID":"eduroam","timestamp":"2018-07-10 12:25:26","longitude":-1.9361,"mac":"dc:d9:16:##:##:##","packet":"PR-REQ","latitude":52.4505,"identifier":"Client"}
My data format (Probe Request with Broadcast / protected SSID):
{"vendor":"Nokia","SSID":"Broadcast","timestamp":"2018-07-10 12:25:26","longitude":-1.9361,"mac":"dc:d9:16:##:##:##","packet":"PR-REQ","latitude":52.4505,"identifier":"Client"}
I'm trying to produce a bubble chart which will display vendors as a bubble (size denoted by volume of packets collected for that vendor) then plot the bubble against X axis unprotected (any SSID != broadcast) & Y axis protected (packets where "Broadcast" is in the data)
Sketch of what I mean
What I've managed to get so far
I've managed to get a bar/ row/pie charts to work as they only require me to use one dimension and run them through a group. But I think I'm fundamentally misunderstanding how to pass multiple dimensions to a group.
for each at the top adds a new value of 0 / 1 to triple if Broadcast is present in the data.
Then I'm using a custom reducer to count 0 / 1 related to unpro & pro which will be used to plot the X / Y
I've tried reworking the nasdaq example and I'm getting nowhere
Code:
queue()
.defer(d3.json, "/uniquedevices")
.await(plotVendor);
function plotVendor(error, packetsJson) {
var packets = packetsJson;
packets.forEach(function (d) {
if(d["SSID"] == "Broadcast") {
d.unpro = 0;
d.pro = 1;
} else {
d.unpro = 1;
d.pro = 0;
}
});
var ndx = crossfilter(packets);
var vendorDimension = ndx.dimension(function(d) {
return [ d.vendor, d.unpro, d.pro ];
});
var vendorGroup = vendorDimension.group().reduce(
function (p, v) {
++p.count;
p.numun += v.unpro;
p.numpr += v.pro;
return p;
},
function (p, v) {
--p.count;
p.numun -= v.unpro;
p.numpr -= v.pro;
return p;
},
function () {
return {
numun: 0,
numpr: 0
};
}
);
var vendorBubble = dc.bubbleChart("#vendorBubble");
vendorBubble
.width(990)
.height(250)
.transitionDuration(1500)
.margins({top: 10, right: 50, bottom: 30, left: 40})
.dimension(vendorDimension)
.group(vendorGroup)
.yAxisPadding(100)
.xAxisPadding(500)
.keyAccessor(function (p) {
return p.key[1];
})
.valueAccessor(function (p) {
return p.key[2];
})
.radiusValueAccessor(function (d) { return Object.keys(d).length;
})
.maxBubbleRelativeSize(0.3)
.x(d3.scale.linear().domain([0, 10]))
.y(d3.scale.linear().domain([0, 10]))
.r(d3.scale.linear().domain([0, 20]))
dc.renderAll();
};
Here is a fiddle: http://jsfiddle.net/adamistheanswer/tm9fzc4r/1/
I think you are aggregating the data right and the missing bits are
your accessors should look inside of value (that's where crossfilter aggregates)
.keyAccessor(function (p) {
return p.value.numpr;
})
.valueAccessor(function (p) {
return p.value.numun;
})
.radiusValueAccessor(function (d) {
return d.value.count;
})
your key should just be the vendor - crossfilter dimensions aren't geometric dimensions, they are what you filter and bin on:
var vendorDimension = ndx.dimension(function(d) {
return d.vendor;
});
you probably need to initialize count because ++undefined is NaN:
function () { // reduce-init
return {
count: 0,
numun: 0,
numpr: 0
};
}
Fork of your fiddle, with all the dependencies added, wrapping function disabled, and elasticX/elasticY (probably not what you want but easier to debug):
https://jsfiddle.net/gordonwoodhull/spw5oxkj/16/

Crossfilter and DC.js: reduce to unique number

In the example below, I am trying to sum by unique occurence of Respond_Id. eg. in this case, it should be in total 3, "Respond_Id" being 258,261 and 345.
This is my data:
{"Respond_Id":258,"Gender":"Female","Age":"18-21","Answer":424},
{"Respond_Id":258,"Gender":"Female","Age":"18-21","Answer":428},
{"Respond_Id":261,"Gender":"Male","Age":"22-26", "Answer":427},
{"Respond_Id":261,"Gender":"Male","Age":"22-26", "Answer":432},
{"Respond_Id":345,"Gender":"Female","Age":"27-30","Answer":424},
{"Respond_Id":345,"Gender":"Female","Age":"27-30","Answer":425},
{"Respond_Id":345,"Gender":"Female","Age":"27-30","Answer":433},
I know I should use group reduce for this, so I tried (adapted from an example):
var ntotal = answerDim.group().reduce(
function(p, d) {
if(d.Respond_Id in p.Respond_Ids){
p.Respond_Ids[d.Respond_Id]++;
}
else {
p.Respond_Ids[d.Respond_Id] = 1;
p.RespondCount++;
}
return p;
},
function (p, d) {
p.Respond_Ids[d.Respond_Id]--;
if(p.Respond_Ids[d.Respond_Id] === 0){
delete p.Respond_Ids[d.Respond_Id];
p.RespondCount--;
}
return p;
},
function () {
return {
RespondCount: 0,
Respond_Ids: {}
};
}
);
Then:
numberDisplay
.group(ntotal)
.valueAccessor(function(d){ return d.value.RespondCount; });
dc.renderAll();
But seems not working. Does someone know how to make it work ? Thank you
Based on your JSFiddle, your setup is like this:
var RespondDim = ndx.dimension(function (d) { return d.Respond_Id;});
var ntotal = RespondDim.group().reduce(
function(p, d) {
if(d.Respond_Id in p.Respond_Ids){
p.Respond_Ids[d.Respond_Id]++;
}
else {
p.Respond_Ids[d.Respond_Id] = 1;
p.RespondCount++;
}
return p;
},
function (p, d) {
p.Respond_Ids[d.Respond_Id]--;
if(p.Respond_Ids[d.Respond_Id] === 0){
delete p.Respond_Ids[d.Respond_Id];
p.RespondCount--;
}
return p;
},
function () {
return {
RespondCount: 0,
Respond_Ids: {}
};
});
What is important to note here is that your group keys, by default, are the same as your dimension keys. So you will have one group per respondent ID. This isn't what you want.
You could switch to using dimension.groupAll, which is designed for this use case, but unfortunately the dimension.groupAll.reduce signature is slightly different. The easiest fix for you is going to be to just define your dimension to have a single value:
var RespondDim = ndx.dimension(function (d) { return true;});
Now you'll see that ntotal.all() will look like this:
{key: true, value: {RespondCount: 3, Respond_Ids: {258: 2, 261: 2, 345: 3}}}
Working fiddle: https://jsfiddle.net/v0rdoyrt/2/

dc.js and crossfilter custom valueAccesor

I am attempting to chart average counts by hour and the custom reduce function is almost working here https://jsfiddle.net/dolomite/6eeahs6z/
There is an issue in that some hours have no activity, e.g. there may be three Sundays in the data but only two have activity:
Date, Hour, Count
Sun 02/07/17, 22, 5
Sun 09/07/17, 22, 3
The data contains the date 25/07/17 but has no records for hour 22. The correct average for hour 22 on Sunday should therefore be 2.66 but the current method is producing an average of 4.
So in short I'm trying to work out how to get total counts per hour and then divide by the number of days in the data, whether or not the selected day has a record for each hour.
The current hour dimension and custom reduce is:
hourDim = ndx.dimension(function (d) {
return d.EventHour;
})
hourAvgGroup = hourDim.group().reduce(
function (p, v) { // add
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
//p.avg = average_map(p.map);
return p;
},
function (p, v) { // remove
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);
//p.avg = average_map(p.map);
return p;
},
function () { // init
return { map: d3.map() };
}
)
The average is computed in the chart valueAccessor as follows:
.valueAccessor(function(d){ return average_map(d.value.map)})
Where
function average_map(m) {
var sum = 0;
m.forEach(function(k, v) {
sum += v;
});
return m.size() ? sum / m.size() : 0;
}
In case anyone is trying to do similar, I created a dimension to hold all records in the data:
allDim = ndx.dimension(function (d) {
return typeof Modality === 'string';
})
Then created a group to hold a map of the number of unique days in the data:
dayCountGroup = allDim.group().reduce(
function (p, v) { // add
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
return p;
},
function (p, v) { // remove
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);
return p;
},
function () { // init
return { map: d3.map() };
}
)
The hour dimension and group are:
hourDim = ndx.dimension(function (d) {
return d.EventHour;
})
hourAvgGroup = hourDim.group().reduce(
function (p, v) { // add
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
return p;
},
function (p, v) { // remove
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);
return p;
},
function () { // init
return { map: d3.map() };
}
)
Then in the value accessor for the barchart i used:
.valueAccessor(function(d){ return sum_map(d.value.map)/size_array_of_maps(dayCountGroup.top(Infinity)) ? sum_map(d.value.map)/size_array_of_maps(dayCountGroup.top(Infinity)) : 0})
Where the two functions used are:
function sum_map(m) {
var sum = 0;
m.forEach(function(k, v) {
sum += v;
});
return m.size() ? sum : 0;
}
function size_array_of_maps(myObject) {
var count = 0;
myObject.forEach(function(key,value) {
count += key.value.map.size();
})
return count;
}
I'm sure there is a lot of redundant code here but the Fiddle seems to be working and I'll tidy it up later :)
https://jsfiddle.net/dolomite/6eeahs6z/126/

Issues filtering dc.seriesChart

I am new to posting a question on stackoverflow, so any guidance is much appreciated!
I am using crossfilter.js and dc.js to plot charts (fairly new to both). One of the requirements is for a seriesChart (scatterplot). Note: I am using the latest beta release, since the scatterplot is a requirement and the latest stable version does not appear support seriesChart & scatterplot. This particular chart is posing me a problem when I filter/zoom. I see the following error in the console when doing so:
Uncaught TypeError: dimension.filterFunction is not a function...
PriceVsTime = dc.seriesChart("#PriceVsTime");
//$('#PriceVsTime').parent('td').addClass('tdOrders1');
PriceVsTimeDimension = crossfilterData.dimension(function (d) { if (d.chart_price > 0) return d.start_datetime; });
PriceVsTimeGroup = PriceVsTimeDimension.group().reduce(
function reduceAdd(p, v) {
++p.count;
p.order_type = v.order_type;
p.execution_type = v.execution_type == 'REPLACED' ? (v.change_qty > 0 ? 'UPSIZE' : 'DOWNSIZE') : v.execution_type;
p.chart_price = v.chart_price;
return p;
},
function reduceRemove(p, v) {
--p.count;
return p;
},
function reduceInitial() {
return { count: 0 };
}
);
var symbolScale = d3.scale.ordinal().range(d3.svg.symbolTypes);
console.log(d3.svg.symbolTypes);
var symbolAccessor = function (d) {
switch (d.value.execution_type) {
case 'NEW':
return d3.svg.symbolTypes[1]; // diamond
case 'CANCELED':
return d3.svg.symbolTypes[0]; // cross
case 'UPSIZE':
return d3.svg.symbolTypes[4]; // triangle-up
case 'DOWNSIZE':
return d3.svg.symbolTypes[5]; // triangle-down
default:
return d3.svg.symbolTypes[3];
}
};
var subChart = function (c) {
return dc.scatterPlot(c)
.existenceAccessor(function (d) { if (d.value.count > 0) { return d.value.execution_type; } })
.symbol(symbolAccessor)
.symbolSize(8)
.highlightedSize(12)
;
};
var PriceVsTimeSeries = function (d) { if (d.value.count > 0) { return d.value.execution_type; } };
var PriceVsTimeKey = function (d) { if (d.value.count > 0) { return d.key; } };
var PriceVsTimeValue = function (d) { if (d.value.count > 0) { return d.value.chart_price; } };
var yPriceVsTime = roundAxis(d3.extent(PriceVsTimeGroup.all(), function (d) { if (d.value.chart_price > 0) { return d.value.chart_price; } }), 10);
function roundAxis(item, interval) {
return [item[0] - item[0] % interval - interval, item[1] - item[1] % interval + interval];
}
PriceVsTime
.chart(subChart)
.width(2 * width1)
.height(height3)
.dimension(PriceVsTimeDimension)
.group(PriceVsTimeGroup)
.seriesAccessor(PriceVsTimeSeries)
.keyAccessor(PriceVsTimeKey)
.valueAccessor(PriceVsTimeValue)
.x(d3.time.scale().domain(DateTimeDomain))
.y(d3.scale.linear().domain(yPriceVsTime))
.yAxisLabel('Price')
.xAxisLabel('Time')
.margins(margin1)
;
PriceVsTime.yAxis().tickFormat(d3.format('s'));
PriceVsTime.brushOn(true).mouseZoomable(true);
PriceVsTime.legend(dc.legend().x(1075).y(0).itemHeight(13).gap(5).horizontal(false).itemWidth(100));

Resources