Complex Attrition Rate Calculation in dc.js - dc.js

I'm looking to add an Attrition Rate % graph to my Dashboard but I'm having difficulty in working out how I would do this using the complex calculation.
Basically the attrition rate % needs to look back at the previous 12 periods - for each period.
Calculation (for each Period) -
Total Starters in Previous 12 Periods (including current) / (Total Heads for Period 13 months ago + Leavers in Previous 12 Periods (including current)).
For example, if the Period is 201710 [YYYYMM], then the calculation would be:
(Total Starters in Periods 201611 to 201710) / Heads in Period 201610 + Total Leavers in Periods 201611 to 201710)
And within the line chart, you'd have the same calculation above for each period.
So, if I have the following data:
{ ... }
{ "Period": 201601, "Heads": 100, "Starters": 10, "Leavers": 8 },
{ "Period": 201602, "Heads": 102, "Starters": 8, "Leavers": 8 },
{ "Period": 201603, "Heads": 102, "Starters": 3, "Leavers": 0 },
{ "Period": 201604, "Heads": 105, "Starters": 8, "Leavers": 12 },
{ "Period": 201605, "Heads": 101, "Starters": 2, "Leavers": 5 },
{ "Period": 201606, "Heads": 98, "Starters": 8, "Leavers": 11 },
{ "Period": 201607, "Heads": 101, "Starters": 6, "Leavers": 5 },
{ "Period": 201608, "Heads": 102, "Starters": 4, "Leavers": 1 },
{ "Period": 201609, "Heads": 105, "Starters": 11, "Leavers": 17 },
{ "Period": 201610, "Heads": 99, "Starters": 8, "Leavers": 11 },
{ "Period": 201611, "Heads": 96, "Starters": 5, "Leavers": 8 },
{ "Period": 201612, "Heads": 95, "Starters": 4, "Leavers": 5 },
{ "Period": 201701, "Heads": 91, "Starters": 1, "Leavers": 5 },
The calculation and attrition rate % for Period 201701 would be:
Starters (Period 201602-201701): 68 / (Heads (Period 201601): 100 + Leavers (Period 201602-201701): 88
Attrition Rate for 201701 is: 36.17%
I would also like to have a number display that shows the attrition rate for the most recent Period.
I have some sample data and a Period chart to work with in a jsfiddle here: https://jsfiddle.net/kevinelphick/nh34aknn/
And a custom reduce function for the group like this:
attritionGroup = dimPeriod.group().reduce(
function (p, d) {
p.heads += d.Heads;
p.starters += d.Starters;
p.leavers += d.Leavers;
return p;
},
function (p, d) {
p.heads -= d.Heads;
p.starters -= d.Starters;
p.leavers -= d.Leavers;
return p;
},
function () {
return {heads: 0, starters: 0, leavers: 0};
});
I appreciate this may be a long shot due to its complex nature and I hope I've described my problem without confusion. I've tried in the past but I can't find any solutions that would work due to my limited knowledge. I can only guess that it would have to loop through the arrays dynamically to sum up starters, leavers that I need for the required Periods? Would I need to get a count of unique Periods to reference the correct periods I need for the calculation?

The reduce sets up the groups for you, then you can use a dummy group that calculates the attrition rates.
(UPDATED code)
function calcAttritionGroup (group) {
return {
all() {
var groupAll = group.all()
groupAll.forEach((p) => {
let elevenMonthsAgo = d3.time.month.offset(p.key, -11)
let twelveMonthsAgo = d3.time.month.offset(p.key, -12)
let twelveMonthsAgoGroup = groupAll.find(function(g){
return g.key.getTime() === twelveMonthsAgo.getTime()
})
let attrHeads = null
if (twelveMonthsAgoGroup) {
attrHeads = twelveMonthsAgoGroup.value.heads;
}
p.attrition = null
if (attrHeads) {
let subgroup = groupAll.filter(function(g) {
return g.key <= p.key && g.key >= elevenMonthsAgo;
})
let attrStarters = subgroup.reduce(function(sum, n) {
return sum + n.value.starters
}, 0)
let attrLeavers = subgroup.reduce(function(sum, n) {
return sum + n.value.leavers
}, 0)
let attrRate = (attrStarters / (attrHeads + attrLeavers))
p.attrition = attrRate || null
})
return groupAll
}
};
}
Here are modifications to your fiddle: https://jsfiddle.net/ga7x1p8m/ (UPDATED)
(Note, the values and formula in your question are different from in the fiddle.)
Some points...
1 - Formatting your period like that is not going to get you far because it won't give you a smooth range for your x scale, and makes it hard to do the comparisons you'll need for getting previous periods. So easiest is probably to cast as date object.
var format = d3.time.format("%Y%m");
data.forEach(function (d) {
d.date = format.parse(d.Period + '')
})
2 - You will have to manage edge cases. What happens if the 12 months previous period can't be found? If the earliest available period is used then this will add some more logic to the calculation.

Related

Jsplumb - Connectors

Am trying to draw a flowchart. I create divs dynamically and have set a unique 'id' property for each div and connect them using Jsplumb connectors.
I get the source and destination id from database(note that 'id' property for div dynamically created is its ID from database) and store in 'connectors' json. Its format is
Eg:
{[from:A,to:B], [from:A,to:C], [from:B,to:C]}
angular.forEach(connectors, function (connect) {
$scope.connection(connect.from, connect.to);
})
The jsplumb code is as follows
$scope.connection = function (s, t) {
var stateMachineConnector1 = {
connector: ["Flowchart", { stub: 25, midpoint: 0.001 }],
maxConnections: -1,
paintStyle: { lineWidth: 3, stroke: "#421111" },
endpoint: "Blank",
anchor: "Continuous",
anchors: [strt, end],
overlays: [["PlainArrow", { location: 1, width: 15, length: 12 }]]
};
var firstInstance = jsPlumb.getInstance();
firstInstance.connect({ source: s.toString(), target: t.toString() }, stateMachineConnector1);
}
THE PROBLEM:
What i have now is
Here the connector B to C overlaps existing A to C connector.
What i need is to separate the two connections like below
I could not find a solution for this anywhere. Any help? Thanks!
Using anchor perimeter calculates the appropriate position for endpoints.
jsfiddle demo for perimeter
jsPlumb.connect({
source:$('#item1'),
target:$("#item2"),
endpoint:"Dot",
connector: ["Flowchart", { stub: 25, midpoint: 0.001 }],
anchors:[
[ "Perimeter", { shape:"Square" } ],
[ "Perimeter", { shape:"Square" } ]
]
});
Jsplumb anchors
What I suggest you to do, to exactly replicate your schema, would be to set 2 endpoints on on box on A, B and C
A Endpoints should be [0.25, 1, 0, 0, 0, 0] and [0.75, 1, 0, 0, 0, 0]
B and C Endpoints should be [0.25, 0, 0, 0, 0, 0] and [0.75, 0, 0, 0, 0, 0]
It basically works like this (I might be wrong for the 4 last one its been a while but you only need to worry about the x and y)
[x,y,offsetx, offsety, angle, angle]
For the x 0 is the extreme left and 1 extreme right
Same goes for y (0 is top and 1 is bottom).
Take care

Kendo UI datepicker - month change event

Kendo UI datepicker - month change event
I searched for this here & on Telerik forum too but don't have the solution for this.
Here, I want to mark few dates from month and I did it on OPEN event like below-
$.each(dates, function (index, date) {
var reformattedDate = date.getFullYear() + '/' + date.getMonth() + '/' + date.getDate();
$('#datepickerId_dateview a.k-link[data-value="' + reformattedDate + '"]').parent().addClass("date-marking-class");
});
So, I am looping though all my dates and comparing it with data-value of datepicker calendar. On match found, I am applying class to mark that date.
It's working absolutely fine on datepicker OPEN event but whenever I change month, it's not marking the date at all.
So i want an event which will trigger on month change, so that I can execute that 2 lines of code to mark the dates on new month.
There does not appear to be anything documented to do this, but after looking at the DatePicker source code you can accomplish it.
The underlying Calendar widget has a navigate event that does what you want(http://docs.telerik.com/kendo-ui/api/javascript/ui/calendar#events-navigate). The problem is getting a reference to the Calendar used by the DatePicker.
I was able to do it like this:
$(document).ready(function() {
// create DatePicker from input HTML element
var datePicker = $("#datepicker").kendoDatePicker().getKendoDatePicker();
var dateView = datePicker.dateView;
// Force calendar to initialize so we can bind to its events...otherwise, it does not exist until it is opened for the first time.
dateView._calendar();
var calendar = dateView.calendar;
calendar.bind("navigate", function () {
console.log("Do your thing here");
});
});
The DatePicker has a DateView which has a Calendar...but the Calendar doesn't exist until the DateView is opened for the first time. But once that happens, you can attach to its navigate event.
I force the Calendar to exist without an open event by calling the "private" _calendar() method that the DateView internally calls on first open...and now you can handle its navigate.
Demo: http://dojo.telerik.com/#Stephen/ekUwE
You can use the month template of the widget:
$("#date").kendoDatePicker({
month: {
content: $("#date-template").html()
}
});
It renders a template for each day if the widget is set to Month view. There you can wrap the day number with a span with the desired class.
And the template could be something like:
#
var month = data.date.getMonth() + 1;
dates = months[month],
found = false,
result = data.value;
if (dates && dates.length > 0) {
for (var i = 0, len = dates.length; i < len; i++) {
var date = dates[i],
dateSplit = data.dateString.split("/");
if (date.getDate() == dateSplit[2] &&
date.getMonth() == dateSplit[1] &&
date.getFullYear() == dateSplit[0])
{
result = "<span class='date-marking-class'>" + data.value + "</span>";
break;
}
}
}
#
#=result#
Being months an object like this:
// All months are contains an array with date objects(in this case, days 10 and 20 for each one)
var months = {
"1": [new Date(2017, 0, 10), new Date(2017, 0, 20)],
"2": [new Date(2017, 1, 10), new Date(2017, 1, 20)],
"3": [new Date(2017, 2, 10), new Date(2017, 2, 20)],
"4": [new Date(2017, 3, 10), new Date(2017, 3, 20)],
"5": [new Date(2017, 4, 10), new Date(2017, 4, 20)],
"6": [new Date(2017, 5, 10), new Date(2017, 5, 20)],
"7": [new Date(2017, 6, 10), new Date(2017, 6, 20)],
"8": [new Date(2017, 7, 10), new Date(2017, 7, 20)],
"9": [new Date(2017, 8, 10), new Date(2017, 8, 20)],
"10": [new Date(2017, 9, 10), new Date(2017, 9, 20)],
"11": [new Date(2017, 10, 10), new Date(2017, 10, 20)],
"12": [new Date(2017, 11, 10), new Date(2017, 11, 20)]
};
Or anyway you want it - that was just a suggestion - since you change the line dates = months[month] to something that gives you an array of dates.
Demo

How to pass raw json string for aggregations in Search Request by calling SearchAsync in ElasticSearch NEST [duplicate]

I get a bit confused and frustrated when it comes to using NEST to querying, as it seems very hit and miss. I have no trouble querying when using standard JSON, so I was wondering if there was some way to query using a JSON object, I have code below
var query = "bkala";
var q = new
{
query = new
{
text = new
{
_all = "jane"
}
}
};
var qJson = JsonConvert.SerializeObject(q);
var hits = client.Search<Users>(qJson);
However, I get the error "Cannot convert from type string to System.Func, Nest.ISearchRequest"
If anyone knows how I can simply query using a JSON object, that would be fantastic, cheers in advance.
NEST and Elasticsearch.Net, the low level client that NEST uses under the covers, are flexible in how you wish to query. With NEST you have a couple of different ways:
NEST - High level client
1.Fluent API
var query = "bkala";
var searchResult = client.Search<MyDocument>(s => s
.Query(q => q
.Match(m => m
.Field("_all")
.Query(query)
)
)
);
Laid out as above, this API uses lambda expressions to define a fluent interface that mimics the structure of the Elasticsearch json API and query DSL.
2.Object Initializer Syntax
var query = "bkala";
var request = new SearchRequest<MyDocument>
{
Query = new MatchQuery
{
Field = "_all",
Query = query
}
};
var searchResult = client.Search<MyDocument>(request);
If lambda expressions are not your thing, then you can always define your searches using specific search types.
Elasticsearch.Net - Low level client
In cases where you would like to query with anonymous types (as per your question), json strings or a byte representation of a query, then you can use the low level client, Elasticsearch.Net, to achieve this. The low level client is exposed on the high level client through the .LowLevel property
1.Anonymous types
var query = new
{
query = new
{
match = new
{
_all = new
{
query = "bkala"
}
}
}
};
var searchResult = client.LowLevel.Search<SearchResponse<MyDocument>>(query);
Using the low level client on the high level client means that you can still take advantage of using Json.NET to deserialize search results; in this example, the search response can be accessed through searchResult.Body
2.Json string
var query = #"
{
""query"": {
""match"": {
""_all"": {
""query"": ""bkala""
}
}
}
}";
var searchResult = client.LowLevel.Search<SearchResponse<MyDocument>>(query);
3.Byte array
var bytes = new byte[] { 123, 13, 10, 32, 32, 34, 113, 117, 101, 114, 121, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 34, 109, 97, 116, 99, 104, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 34, 95, 97, 108, 108, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 113, 117, 101, 114, 121, 34, 58, 32, 34, 98, 107, 97, 108, 97, 34, 13, 10, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 125, 13, 10, 32, 32, 125, 13, 10, 125 };
var searchResult = client.LowLevel.Search<SearchResponse<MyDocument>>(bytes);
All of the above methods produce the following query
{
"query": {
"match": {
"_all": {
"query": "bkala"
}
}
}
}
Check out the Getting started guide on the github repo as well as the documentation on the Elastic website. We are continually working to improve documentation and PRs are more than welcome for areas where you feel we are lacking :)

Ctrl-S input event in Windows console with ReadConsoleInputW

I am using ReadConsoleInputW to read Windows 10 console input. I want to be able to detect when Ctrl+S is pressed. Using the code I have, I can detect Ctrl+Q without issue, but I'm not seeing anything for Ctrl+S. Is Ctrl+S even detectable?
The below is the sequence of INPUT_RECORD I read when pressing Ctrl+S a few times, followed by Ctrl+Q.
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 17, scan_code: 29, wide_char: 0, control_key_state: 40 }
Key { key_down: true, repeat_count: 1, key_code: 81, scan_code: 16, wide_char: 17, control_key_state: 40 }
If it matters, this is in Rust using wio.
Calling SetConsoleMode with ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS as the second argument (thus disabling ENABLE_PROCESSED_INPUT) did the trick.
oconnor0's answer helped me find the solution.
However, I could not get ctrl-s event by disabling ENABLE_PROCESSED_INPUT, so I tried using only ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS as suggested by oconnor0. This worked, but this means ENABLE_PROCESSED_INPUT is not the culrpit!
So I tried:
//This didn't work
if (!GetConsoleMode(hConsoleInput, &lpMode)) Error();
lpMode &= ~(ENABLE_PROCESSED_INPUT);
if (!SetConsoleMode(hConsoleInput, lpMode)) Error();
//This worked
if (!GetConsoleMode(hConsoleInput, &lpMode)) Error();
lpMode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
if (!SetConsoleMode(hConsoleInput, lpMode)) Error();
Disabling ENABLE_ECHO_INPUT forces you to disable ENABLE_ECHO_INPUT (see msdn), but it isn't the culprit because:
//This didn't work either
if (!GetConsoleMode(hConsoleInput, &lpMode)) Error();
lpMode &= ~(ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT);
if (!SetConsoleMode(hConsoleInput, lpMode)) Error();
So this means that ENABLE_LINE_INPUT is the culprit!
It's not clear why though:
ENABLE_LINE_INPUT 0x0002 The ReadFile or ReadConsole function returns
only when a carriage return character is read. If this mode is
disabled, the functions return when one or more characters are
available.

AM Charts initial view using zoomToDates

Been stumped on this for a little bit.
I found some other help on zoomToIndexes, but I cant get the zoomToDates to work on my page.
Live page is
b2 resource urq sales
Im trying to set the initial view to show from 2000 to current.. I want to slap some original sales data from early 80's in the graph, but dont want the graph to initially show the last 30+ years..
Any help would be MUCH appreciated!
zoomToDates takes real JavaScript Date objects as parameters:
chart.zoomToDates(new Date(2005, 0, 1), new Date(2015, 11, 31));
You can use chart's rendered event to "pre-zoom" on load as well:
var chart = AmCharts.makeChart("chartdiv", {
// your chart config
// ...
});
chart.addListener("rendered", function(event) {
event.chart.zoomToDates(new Date(2005, 0, 1), new Date(2015, 11, 31));
});
Note, that months in Date() constructor parameter (second parameter) are zero-based. Meaning January is 0, February - 1, etc.
You should use valueAxis property of chart object for zoomToValues. I hope this might help you.
var chart= AmCharts.makeChart("chartdiv", {
"type": "gantt",
"theme": "black",
...
});
zoomChart();
chart.addListener("dataUpdated", zoomChart);
function zoomChart(event) {
chart.valueAxis.zoomToValues(new Date(2017, 2, 10), new Date(2017,2,12));
// or ==> event.chart.valueAxis.zoomToValues(new Date(2017, 2, 10), new Date(2017,2,12));
}
This worked for me.:
var chart = AmCharts.makeChart('chartdiv', {
type: 'serial',
...
});
chart.addListener('dataUpdated', zoomChart);
zoomChart();
function zoomChart() {
chart.zoomToDates(new Date(2018, 2, 26), new Date(2018, 2, 28));
}
Note: The months parameter in Date() constructor are zero-based. January is 0, February is 1 and etc.

Resources