2

Problem description

I have a large data array with a structure similar to the following and seek to create a chart that will display the timerecords' changesets by hour that the changeset was created.

[ // array of records
    {
        id: 1,
        name: 'some record',
        in_datetime: '2019-10-24T08:15:00.000000',
        out_datetime: '2019-10-24T10:15:00.000000',
        hours: 2,
        tasks: ["some task name", "another task"],
        changesets: [ //array of changesets within a record
            {
                id: 1001,
                created_at: '2019-10-24T09:37:00.000000'
            },
            ...
        ]
    },
    ...
]

No matter how I have tried to create the dimension/write reduction functions I can't get the correct values out of the data table.

const changeReduceAdd = (p, v) => {
    v.changesets.forEach(change => {
        let cHour = timeBand[change.created_hour]
        if (showByChanges) {
            p[cHour] = (p[cHour] || 0) + (change.num_changes || 0)
        } else {
            p[cHour] = (p[cHour] || 0) + 1 //this is 1 changeset
        }
    })
    return p
}

const changeReduceRemove = (p, v) => {
    v.changesets.forEach(change => {
        let cHour = timeBand[change.created_hour]
        if (showByChanges) {
            p[cHour] = (p[cHour] || 0) - (change.num_changes || 0)
        } else {
            p[cHour] = (p[cHour] || 0) - 1 //this is 1 changeset
        }
    })
    return p
}

const changeReduceInit = () => {
    return {}
}

//next create the array dimension of changesets by hour
//goal: show changesets or num_changes grouped by their created_hour
let changeDim = ndx.dimension(r => r.changesets.map(c => timeBand[c.created_hour]), true)
let changeGroup = changeDim.group().reduce(changeReduceAdd, changeReduceRemove, changeReduceInit)
let changeChart = dc.barChart('#changeset-hour-chart')
    .dimension(changeDim)
    .keyAccessor(d => d.key)
    .valueAccessor(d => d.value[d.key])
    .group(changeGroup)

jsfiddle and debugging notes

The main problem I'm having is I want the changesets/created_hour chart, but in every dimension I have tried, where the keys appear correct, the values are significantly higher than the expected.

The values in the "8AM" category give value 5, when there are really only 3 changesets which I marked created_hour: 8:

wrong histogram

Gordon
  • 19,811
  • 4
  • 36
  • 74
Shawn Pacarar
  • 414
  • 3
  • 12
  • Your fiddle is using crossfilter version 1.3, which doesn't support tag dimensions. It works somewhat better with crossfilter 1.4: https://jsfiddle.net/gordonwoodhull/5utLn8oz/ – Gordon Nov 12 '19 at 15:14
  • Could you possibly edit your question to make it shorter? The NaN thing is not relevant and (with all due respect ;) it's also not very interesting to debug differences between your local and your fiddle. I'm not clear what the actual question is. – Gordon Nov 12 '19 at 15:17
  • thanks the versioning was the difference between local/fiddle so that is taken out. sorry for the clutter. The fiddle was updated to use the new version, question simplified to show that it's the values in the changeset/hour table that is the problem and returning unexpected values – Shawn Pacarar Nov 12 '19 at 16:21
  • Good question, thanks. I'm looking into it now. – Gordon Nov 12 '19 at 16:28

1 Answers1

3

There are a lot of solutions to the "tag dimension" problem, and you happen to have chosen two of the best.

Either

  1. the custom reduction, or
  2. the array/tag flag parameter to the dimension constructor

would do the trick.

Combining the two techniques is what got you into trouble. I didn't try to figure what exactly was going on, but you were somehow summing the counts of the hours.

Simple solution: use the built-in tag dimension feature

Use the tag/dimension flag and default reduceCount:

let changeDim = ndx.dimension(r => r.changesets.map(c => timeBand[c.created_hour]), true)
let changeGroup = changeDim.group(); // no reduce, defaults to reduceCount
let changeChart = dc.barChart('#changeset-hour-chart')
    .dimension(changeDim)
    .keyAccessor(d => d.key)
    .valueAccessor(d => d.value) // no [d.key], value is count
    .group(changeGroup)

correct histogram

fork of your fiddle

Manual, pre-1.4 groupAll version

You also have a groupAll solution in your code. This solution was necessary before array/tag dimensions were introduced in crossfilter 1.4.

Out of curiosity, I tried enabling it, and it also works once you transform from the groupAll result into group results:

function groupall_map_to_group(groupall) {
  return {
    all: () => Object.entries(groupall.value())
                     .map(([key,value])=>({key,value}))
  }
}

let changeGroup = ndx.groupAll().reduce(changeReduceAdd, changeReduceRemove, changeReduceInit)
let changeChart = dc.barChart('#changeset-hour-chart')
    .dimension({}) // filtering not implemented
    .keyAccessor(d => d.key)
    .valueAccessor(d => d.value) // [d.key]
    .group(groupall_map_to_group(changeGroup))
    .x(dc.d3.scaleBand().domain(timeBand))
    .xUnits(dc.units.ordinal)
    .elasticY(true)
    .render()

crossfilter 1.3 version

Gordon
  • 19,811
  • 4
  • 36
  • 74
  • Wow that's interesting, I did not expect the tag dimension to simply work like that. Is there an easy way to sum the `num_changes` for the changesets in that bin? – Shawn Pacarar Nov 12 '19 at 17:24
  • I don’t see `num_changes` in your data set. Do you man doing a `reduceSum` on `changesets.length`? – Gordon Nov 12 '19 at 18:47
  • `num_changes` is a property of the changesets – Shawn Pacarar Nov 12 '19 at 19:22
  • [reduceSum documentation](https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_reduceSum) – Gordon Nov 12 '19 at 19:35
  • since it's a nested property within the timerecord reduceSum gets called for each timerecord for every changeset that it has. is there a way for the reduceSum to know what key it is calculating for so I could reduce it something like `.group().reduceSum(d => { return d.changesets.reduce((acc, c) => { if(c.created_hour matches the group's key) { return acc + c.num_changes } else { return acc } }) })` – Shawn Pacarar Nov 12 '19 at 19:43
  • 1
    See the second part of this answer? https://stackoverflow.com/a/58135255/676195 – Gordon Nov 12 '19 at 20:00