Skip to content

Commit

Permalink
Heatmap wip
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwatkins73 committed Jul 8, 2020
1 parent 9c0461f commit a6ed97e
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.khartec.waltz.data.changelog;

import com.khartec.waltz.data.GenericSelector;
import com.khartec.waltz.model.EntityKind;
import com.khartec.waltz.model.EntityReference;
import com.khartec.waltz.model.tally.ChangeLogTally;
Expand Down Expand Up @@ -75,20 +76,19 @@ public ChangeLogSummariesDao(DSLContext dsl) {
}


public List<DateTally> findCountByDateForParentKindBySelector(EntityKind parentKind,
Select<Record1<Long>> selector,
public List<DateTally> findCountByDateForParentKindBySelector(GenericSelector selector,
Optional<Integer> limit) {
checkNotNull(parentKind, "parentKind must not be null");
checkNotNull(selector, "selector must not be null");

Field<Date> date = DSL.date(CHANGE_LOG.CREATED_AT);

return dsl.select(date, DSL.count(CHANGE_LOG.ID))
.from(CHANGE_LOG)
.where(CHANGE_LOG.PARENT_ID.in(selector)
.and(CHANGE_LOG.PARENT_KIND.eq(parentKind.name())))
.where(CHANGE_LOG.PARENT_ID.in(selector.selector())
.and(CHANGE_LOG.PARENT_KIND.eq(selector.kind().name())))
.groupBy(date)
.orderBy(date.desc())
.limit(limit.orElse(Integer.MAX_VALUE))
.limit(limit.orElse(365))
.fetch(TO_DATE_TALLY_MAPPER);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import com.khartec.waltz.data.changelog.ChangeLogSummariesDao;
import com.khartec.waltz.model.EntityKind;
import com.khartec.waltz.model.HierarchyQueryScope;
import com.khartec.waltz.model.IdSelectionOptions;
import com.khartec.waltz.model.tally.DateTally;
import com.khartec.waltz.service.DIBaseConfiguration;
import com.khartec.waltz.service.DIConfiguration;
import com.khartec.waltz.service.changelog.ChangeLogService;
import org.jooq.DSLContext;
import org.jooq.DatePart;
import org.jooq.Record1;
Expand All @@ -44,23 +47,23 @@ public class ChangeLogSummariesHarness {

public static void main(String[] args) {

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DIBaseConfiguration.class);
ChangeLogSummariesDao dao = ctx.getBean(ChangeLogSummariesDao.class);
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DIConfiguration.class);
ChangeLogService svc = ctx.getBean(ChangeLogService.class);
DSLContext dsl = ctx.getBean(DSLContext.class);

dsl.update(CHANGE_LOG)
.set(CHANGE_LOG.CREATED_AT,
DSL.timestampSub(
CHANGE_LOG.CREATED_AT,
CHANGE_LOG.ID.plus(CHANGE_LOG.PARENT_ID).mod(250),
DSL.now(),
CHANGE_LOG.ID.plus(CHANGE_LOG.PARENT_ID).mod(360),
DatePart.DAY))
.execute();

Select<Record1<Long>> selector = new ApplicationIdSelectorFactory().apply(
mkOpts(mkRef(EntityKind.ORG_UNIT, 20), HierarchyQueryScope.CHILDREN));

IdSelectionOptions opts = mkOpts(mkRef(EntityKind.ORG_UNIT, 20), HierarchyQueryScope.CHILDREN);

List<DateTally> res = time("findCountByDateForParentKindBySelector", () ->
dao.findCountByDateForParentKindBySelector(EntityKind.APPLICATION, selector, Optional.empty()));
svc.findCountByDateForParentKindBySelector(EntityKind.APPLICATION, opts, Optional.empty()));

System.out.println(res);
}
Expand Down
19 changes: 16 additions & 3 deletions waltz-ng/client/change-log/services/change-log-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* See the License for the specific
*
*/
import {checkIsEntityRef} from "../../common/checks";
import {checkIsEntityRef, checkIsIdSelector} from "../../common/checks";


function store($http, BaseApiUrl) {
Expand All @@ -41,10 +41,18 @@ function store($http, BaseApiUrl) {
$http.get(`${BASE}/user/${userName}`, {params: {limit}})
.then(r => r.data);

const findSummaries = (kind, options, limit = null) => {
checkIsIdSelector(options);
return $http
.post(`${BASE}/summaries/${kind}`, options, {params: {limit}})
.then(r => r.data);
}

return {
findByEntityReference,
findUnattestedChangesByEntityReference,
findForUserName
findForUserName,
findSummaries,
};
}

Expand All @@ -71,7 +79,12 @@ export const ChangeLogStore_API = {
findForUserName: {
serviceName,
serviceFnName: "findForUserName",
description: "'finds change log entries for a given user name and limit (default: no limit)"
description: "finds change log entries for a given user name and limit (default: no limit)"
},
findSummaries: {
serviceName,
serviceFnName: "findSummaries",
description: "finds tallies by date for all changes of the given kind for entities related to the given selector [desiredKind, selector, limit]"
}
};

Expand Down
173 changes: 114 additions & 59 deletions waltz-ng/client/playpen/3/calendar-heatmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,22 @@ import {nest} from "d3-collection";
import {scaleLinear} from "d3-scale";
import {select} from "d3-selection";
import moment from "moment";
import {CORE_API} from "../../common/services/core-api-utils";
import {mkSelectionOptions} from "../../common/selector-utils";

global.moment = moment;

const bindings = {
config: "<"
parentEntityRef: "<"
};


function getDates(start, stop) {
const dateArray = [];
let currentDate = moment(start);
const stopDate = moment(stop);
while (currentDate <= stopDate) {
dateArray.push( moment(currentDate).format("YYYY-MM-DD") )
currentDate = moment(currentDate).add(1, "days");
}
return dateArray;
}

const NUM_WEEKS = 53;

const COLORS = {
cellBorder: "#93d489",
filledCellRange: ["#e7fae2", "#7df563"],
emptyCellFill: "#fafafa",
emptyCellBorder: "#ddd",
label: "#8b8888"
};

const DIMENSIONS = {
margins: {
Expand All @@ -48,76 +45,116 @@ const DIMENSIONS = {
top: 50,
bottom: 10
},
cellSize: 14
cellSize: 14,
fontSize: 12
}


const initData = {

};

const dates = getDates(
moment().subtract( 1, "years"),
moment());

global.moment = moment;
global.dates = dates;

function mkRawData() {

const dayDist = {
0: 20,
1: 70,
2: 80,
3: 90,
4: 100,
5: 70,
6: 10
};
function getDates(start, stop) {
const dateArray = [];
let currentDate = moment(start);
const stopDate = moment(stop);
while (currentDate <= stopDate) {
dateArray.push( moment(currentDate).format("YYYY-MM-DD") )
currentDate = moment(currentDate).add(1, "days");
}
return dateArray;
}


function prepareData(data = []) {

const rawDates = getDates(
moment().subtract( 12, "months"),
moment());

const dataByDate = _.keyBy(data, "date");

return _
.chain(dates)
.chain(rawDates)
.map(d => {
const dt = moment(d);
const day = dt.day();
const val = Math.random() * dayDist[day];
return {
dateStr: d,
date: moment(d),
day,
val
count: _.get(dataByDate, [d, "count"], 0)
};
})
.value();
}


function prepareData(data) {
function nestData(preparedData = []) {
const byYearWeek = nest()
.key(d => d.date.year())
.key(d => d.date.week())
.entries(data);
.entries(preparedData);

let acc = 0;
_.forEach(byYearWeek, d => {
d.offset = acc;
acc += d.values.length ;
_.forEach(d.values, (v,idx) => v.offset = d.offset + idx)
d.endOffset = acc;
});

return byYearWeek;
}

function draw(data, holder) {
const maxOffset = _.max(_.map(data, "endOffset"));

function drawDayLabels(svg) {
svg.append("g")
.classed("wch-day-labels", true)
.attr("transform", `translate(${DIMENSIONS.margins.left - 5}, ${DIMENSIONS.margins.top})`)
.selectAll("text.wch-day-label")
.data([{label: "Mon", day: 1}, {label: "Wed", day: 3}, {label: "Fri", day: 5}])
.enter()
.append("text")
.classed("wch-day-label", true)
.attr("text-anchor", "end")
.attr("fill", COLORS.label)
.attr("font-size", DIMENSIONS.fontSize)
.attr("dy", d => DIMENSIONS.cellSize * d.day + DIMENSIONS.fontSize - 2)
.text(d => d.label);
}


function drawMonthLabels(svg, rawData, nestedData) {
global.rawData = rawData;
global.nestedData = nestedData;
let t = _.groupBy(rawData, d => d.date.year() * 100 + d.date.month());
console.log({rawData, nestedData, t})

}


function draw(rawData, holder) {
const nestedData = nestData(rawData);
const maxOffset = _.max(_.map(nestedData, "endOffset"));

const w = DIMENSIONS.margins.left + DIMENSIONS.margins.right + (maxOffset * DIMENSIONS.cellSize);
const h = DIMENSIONS.margins.top + DIMENSIONS.margins.bottom + (7 * DIMENSIONS.cellSize)
const h = DIMENSIONS.margins.top + DIMENSIONS.margins.bottom + (7 * DIMENSIONS.cellSize);

const colorScale = scaleLinear()
.domain([0, 8])
.range(COLORS.filledCellRange);

const svg = select(holder)
.append("svg")
// .style("width", "100%")
.style("border", "1px dashed red")
.attr("viewBox", `0 0 ${w} ${h}`)
.attr("width", 900)
.attr("preserveAspectRatio", "xMinYMin meet");

drawDayLabels(svg);



const gYears = svg
.append("g")
Expand All @@ -126,11 +163,21 @@ function draw(data, holder) {

const gWeeks = gYears
.selectAll("g.wch-year")
.data(data)
.data(nestedData)
.enter()
.append("g")
.classed("wch-year", true)
.attr("transform", d => `translate(${DIMENSIONS.cellSize * d.offset}, 0)`)
.attr("transform", d => `translate(${DIMENSIONS.cellSize * d.offset }, 0)`)

const foo = gWeeks
.selectAll("text.wch-month-label")
.data(d => d.values)
.enter()
.append("text")
.classed(".wch-month-label", true)
.attr("transform", (d, idx) => `translate(${idx * (DIMENSIONS.cellSize)}, -16)`)
.attr("foo", d => console.log(d))
.text(d => moment().week(d.key).month())

const gWeek = gWeeks
.selectAll("g.wch-week")
Expand All @@ -140,10 +187,6 @@ function draw(data, holder) {
.classed(".wch-week", true)
.attr("transform", (d, idx) => `translate(${idx * (DIMENSIONS.cellSize)}, 0)`);

const colorScale = scaleLinear()
.domain([20, 100])
.range(["#e7fae2", "#7df563"]);

gWeek
.selectAll("rect.wch-day")
.data(d => d.values)
Expand All @@ -155,20 +198,28 @@ function draw(data, holder) {
.attr("rx", 2)
.attr("ry", 2)
.attr("y", (d, i) => d.date.day() * DIMENSIONS.cellSize)
.attr("fill", (d) => d.val > 20
? colorScale(d.val)
: "#fafafa")
.attr("stroke", d => d.val > 20
? "#93d489"
: "#ddd")
.on("mouseover", d => console.log(d.date.day(), d.val))
.attr("fill", (d) => d.count === 0
? COLORS.emptyCellFill
: colorScale(d.count))
.attr("stroke", d => d.count > 20
? COLORS.cellBorder
: COLORS.emptyCellBorder)
.on("mouseover", d => console.log(d.dateStr, d.count, {d}))
}

function controller($element) {

function controller(serviceBroker, $element) {
const vm = initialiseData(this, initData);

vm.$onInit = () => {
draw(prepareData(mkRawData()), $element[0]);
const selectionOptions = mkSelectionOptions(vm.parentEntityRef);
serviceBroker
.loadViewData(
CORE_API.ChangeLogStore.findSummaries,
["APPLICATION", selectionOptions, 365])
.then(r => {
draw(prepareData(r.data), $element[0]);
})
};

vm.$onChanges = () => {
Expand All @@ -177,7 +228,11 @@ function controller($element) {
}


controller.$inject = ["$element"];
controller.$inject = [
"ServiceBroker",
"$element"
];


export default {
id: "waltzCalendarHeatmap",
Expand Down
Loading

0 comments on commit a6ed97e

Please sign in to comment.