Crossfilter reduce :: find number of uniques - crossfilter

I am trying to create a custom reduce function for a dataset attribute group that would sum a number of unique values for another attribute.
For example, my dataset looks like a list of actions on projects by team members:
{ project:"Website Hosting", teamMember:"Sam", action:"email" },
{ project:"Website Hosting", teamMember:"Sam", action:"phoneCall" },
{ project:"Budjet", teamMember:"Joe", action:"email" },
{ project:"Website Design", teamMember:"Joe", action:"design" },
{ project:"Budget", teamMember:"Sam", action:"email" }
So, team members work on a variable number of projects by performing one action per line. I have a dimension by team member, and would like to reduce it by the number of projects (uniques).
I tried the below (storing project in a uniques array) without success (sorry, this might hurt your eyes):
var teamMemberDimension = dataset.dimension(function(d) {
return d.teamMember;
});
var teamMemberDimensionGroup = teamMemberDimension.group().reduce(
// add
function(p,v) {
if( p.projects.indexOf(v.project) == -1 ) {
p.projects.push(v.project);
p.projectsCount += 1;
}
return p;
},
// remove
function(p,v) {
if( p.projects.indexOf(v.projects) != -1 ) {
p.projects.splice(p.projects.indexOf(v.projects), 1);
p.projectsCount -= 1;
}
return p;
},
// init
function(p,v) {
return { projects:[], projectsCount:0 }
}
);
Thanks a lot!
Edit after DJ Martin's answer ::
So, to be clearer, I would like to get the numbers I am after here would be:
-----------
Sam : 2 (projects he is workin on, no matter the number of actions)
Joe : 2 (projects he is workin on, no matter the number of actions)
-----------
The answer provided by DJ Martin gets me there. But rather than hard coding a table, I would like to find a way to use these numbers for my DC.JS bar chart. When I was only using the number of actions (so just a reduceCount() ), I did it like below:
teamMemberChart.width(270)
.height(220)
.margins({top: 5, left: 10, right: 10, bottom: 20})
.dimension(teamMemberDimension)
.group(teamMemberDimensionGroup)
.colors(d3.scale.category20())
.elasticX(true)
.xAxis().ticks(4);
I guess there might be something to change in the group().

UPDATED ANSWER
Sorry I misunderstood the question... you are actually on the right track. You'll just need to maintain a count of each project so that your subtract function can know when to remove the value.
teamMemberGroup = teamMemberDimension.group().reduce(
function (p, d) {
if( d.project in p.projects)
p.projects[d.project]++;
else p.projects[d.project] = 1;
return p;
},
function (p, d) {
p.projects[d.project]--;
if(p.projects[d.project] === 0)
delete p.projects[d.project];
return p;
},
function () {
return {projects: {}};
});
Here is an updated fiddle: http://jsfiddle.net/djmartin_umich/3LyhL/

Related

Retrieve and compare the style attribute of an element periodically using using cypress

I have a time indicator that travels over a timescale, the indicator's style attribute value keeps on changing for every x milliseconds and I need to get, store and compare that the previously captured value is greater than the latest value.
Initial value:
Latest value:
The logic is, from one point (left 10), every second it moves to the left (left -0, -1, -2, -3 ...)
I tried few ways and one of them is to capture in the same 'cy.then', but in that case, the element will not have the recent value. So far, I tried this. it fetches the value and with some help of regex, I got a 'comparable' value but how I can store/compare those values? Additionally, what is the best way if we need to compare more than 2 values?
const BTN_CONTROL_TIMEINDICATOR = '#currentTimeIndicator'
static verifyTimeLapse() {
//wip
var initialVal, nextVal
initialVal = this.getAnyValueOfAnElement(BTN_CONTROL_TIMEINDICATOR)
cy.wait(500)
nextVal = this.getAnyValueOfAnElement(BTN_CONTROL_TIMEINDICATOR)
cy.log(initialVal > nextVal)
}
static getAnyValueOfAnElement(element) {
//wip
cy.get(element)
.then(($ele) => {
const val=$ele.attr('style').replace(/[^\d.-]/g, '')
cy.log(val)
// return does not work
})
}
cy.log:
Page objects don't work very well with the Cypress command queue, here's what you might do with custom commands.
/* Get the numeric value of CSS left in px */
Cypress.Commands.add('getTimescaleValue', () => {
cy.get('#currentTimeIndicator')
.then($el => +$el[0].style.left.replace('px',''))
})
/* Get a sequence of time scale values */
Cypress.Commands.add('getTimescaleValues', ({numValues, waitBetween}) => {
const values = [];
Cypress._.times(numValues, () => { // repeat inner commands n times
cy.getTimescaleValue()
.then(value => values.push(value)) // save value
.wait(waitBetween)
})
return cy.wrap(values);
})
/* Assert a sequence of values are in descending order */
Cypress.Commands.add('valuesAreDescending', { prevSubject: true }, (values) => {
values.reduce((prev, current) => {
if (prev) { // skip first (no prev to compare)
expect(prev).to.be.gt(current) // assert pairs of values
}
return current
});
})
it('check the timeline', () => {
cy.getTimescaleValues({ numValues: 10, waitBetween: 100 })
.valuesAreDescending()
})
Log
assert
expected 63 to be above 58
assert
expected 58 to be above 48
assert
expected 48 to be above 43
assert
expected 43 to be above 33
assert
expected 33 to be above 23
assert
expected 23 to be above 18
assert
expected 18 to be above 13
assert
expected 13 to be above 3
assert
expected 3 to be above -2
Tested with
<div id="currentTimeIndicator" style="left:63px">Target</div>
<script>
const timer = setInterval(() => {
const div = document.querySelector('#currentTimeIndicator')
const left = +div.style.left.replace('px', '');
if (left < 0) {
clearInterval(timer)
return
}
const next = (left - 5) + 'px';
div.style.left = next;
}, 100)
</script>
If your app uses setInterval() for timing, you should be able to use cy.clock() and cy.tick() instead of .wait(waitBetween) to get more precise sampling and faster test execution.
I don't know where the initial value comes from. But before it changes, maybe on page load, maybe as first job on click, etc you can do something like this:
let item = document.querySelector("#currentTimeIndicator");
item.dataset.left = parseFloat(item.style.left);
console.log(item);
<div id="currentTimeIndicator" style="left:-20px"></div>

How to query exactly selected items in Paper.js?

According to my understanding, project.getItems({selected: true}) returns wrong results: I'm selecting a curve, it returns the parent Path: Sketch
Try clicking on a curve or a segment. Whole path will be moved. Then try changing the behavior by setting var workaround = false to var workaround = true to observe desired behavior.
How can I get exactly what is really selected?
Current workaround
I'm currently adding those objects into an array on selection and use those items instead of project.getItems({selected: true}).
The thing is that in Paper.js architecture, curves and segments are not items (they are part of a specific item which is the path). So you shouldn't expect project.getItems() to return anything else than items.
Another thing you have to know is that a path is assumed selected if any of its part is selected (curves, segments, points, handles, position, bounds, ...). And a curve is assumed selected if all of its parts are selected (points and handles).
With that in mind, you can create an algorithm to retrieve "what is really selected" based on project.getItems({selected: true}) as its first part. Then, you need to loop through curves and segments to check if they are selected.
Here is a sketch demonstrating a possible solution.
var vector = new Point(10, 10);
// Create path.
var path = new Path({
segments: [
[100, 100],
[200, 100],
[260, 170],
[360, 170],
[420, 250]
],
strokeColor: 'red',
strokeWidth: 10
});
// Translate given thing along global vector.
function translateThing(thing) {
switch (thing.getClassName()) {
case 'Path':
thing.position += vector;
break;
case 'Curve':
thing.segment1.point += vector;
thing.segment2.point += vector;
break;
case 'Segment':
thing.point += vector;
break;
}
}
// On mouse down...
function onMouseDown(event) {
// ...only select what was clicked.
path.selected = false;
hit = paper.project.hitTest(event.point);
if (hit && hit.location) {
hit.location.curve.selected = true;
}
else if (hit && hit.segment) {
hit.segment.selected = true;
}
// We check all items for demo purpose.
// Move all selected things.
// First get selected items in active layer...
project.activeLayer.getItems({ selected: true })
// ...then map them to what is really selected...
.map(getSelectedThing)
// ...then translate them.
.forEach(translateThing);
}
// This method returns what is really selected in a given item.
// Here we assume that only one thing can be selected at the same time.
// Returned thing can be either a Curve, a Segment or an Item.
function getSelectedThing(item) {
// Only check curves and segments if item is a path.
if (item.getClassName() === 'Path') {
// Check curves.
for (var i = 0, l = item.curves.length; i < l; i++) {
if (item.curves[i].selected) {
return item.curves[i];
}
}
// Check segments.
for (var i = 0, l = item.segments.length; i < l; i++) {
if (item.segments[i].selected) {
return item.segments[i];
}
}
}
// return item by default.
return item;
}
That said, depending on your real use case, your current workaround could be more appropriate than this approach.

For Each Loop With If Statement - Performance

I am having a complex object, and I am trying to loop through the objects and add to an another list.
But as I have few if statements inside the loop to check whether the object inside is null OR not, the iteration is taking lot of time. Also I am looping through around 70000 items.
Below is the code,
var Product = model; //complex object
Parallel.ForEach({model, product => {
if(product.Type != null)//type a
{ A = a.Loca;//do something }
if(product.Type != null)//type b
{ B = b.Loca;//do something }
if(product.Type != null)//type c
{ A = c.Loca;//do something }
dataAsset.Push(new assetItems(A, B, C));
}
});
I am trying to improve the performance.
Improve the performance by only checking if product.Type != null once. You do not need to check it three separate times. i.e.
Parallel.ForEach({model, product => {
if(product.Type != null)
{ a;//do something
b;//do something
c;//do something
}
dataAsset.Push(new assetItems(a, b, c));
}
By using Elvis operator I was able to improve the performance.

Using find() to match multiple conditions

I have been using this code to find whether a school exists in a collection or not
var sn = 'mit';
var schoolexists = Schools.find({schoolname: sn}, {limit: 1}).count() > 0;
if(schoolexists == true){
alert('school already exists');
}
This works but i now need to introduce two more pointed conditions like schoollocation,studentid and get only the records that satisfy those three conditions. How would i introduce the two extra conditions?.
Just add them to the selector (the first argument):
var selector = {
schoolName: 'mit',
shoolLocation: 'cambridge',
studentId: 'abc123'
}
var schoolexists = Schools.find(selector, {limit: 1}).count() > 0;
Selector fields are ANDed together.

How to define the number of columns to show on Jquery Full Calendar?

I'm using the week view, but instead of showing 7 columns per slide I want to show three columns, is it possible to archive this?
I failed to see any related method on the official documentation: http://fullcalendar.io/docs/
Version 2.2.5+ of Full Calendar has this kind of customization built in.
You just need to do something like this:
views: {
agendaThreeDay: {
type: 'agenda',
duration: { days: 3 },
buttonText: '3 day'
},
defaultView: 'agendaThreeDay'
}
You can get more information on this from the document page here.
Pull the source, use this code (may need some additional change).
src/agenda/AgendaThreeDayView.js
fcViews.agendaThreeDay = AgendaThreeDayView;
function AgendaThreeDayView(a) {
AgendaView.call(this, a);
}
AgendaThreeDayView.prototype = createObject(AgendaView.prototype);
$.extend(AgendaThreeDayView.prototype, {
name: "agendaThreeDay",
incrementDate: function(a, b) {
return a.clone().stripTime().add(b, "days");
},
render: function(a) {
this.intervalStart = a.clone().stripTime();
this.intervalEnd = this.intervalStart.clone().add(3, "days");
this.start = this.skipHiddenDays(this.intervalStart);
this.end = this.skipHiddenDays(this.intervalEnd, -1, true);
this.title = this.calendar.formatRange(this.start, this.end.clone().subtract(1), this.opt("titleFormat"), " — ");
AgendaView.prototype.render.call(this, 3);
}
});
Edit:
Remembered that you need to add the file to lumbar.json
Look here for how to build: https://github.com/arshaw/fullcalendar/wiki/Contributing-Code

Resources