How do you create a pie chart of subitems using a crossfiltered powered dc.js chart?

68 Views Asked by At

Suppose I have a list of customers with their orders:

const customers = [
  { id: 1, orders: [{ products: ["apple", "banana"] }, { products: ["apple"] }] },
  { id: 2, orders: [{ products: ["strawberry"] }] },
  { id: 3, orders: [{ products: ["apple"] }, { products: ["strawberry"] }] },
  { id: 4, orders: [] }
]

I would like to see a pie chart that shows a slice for every single product available. When filtering, I would like to click on "apple" and filter by the customers who ordered apples at any point in time.

I'm currently able to render the pie chart but it shows every possible combination of products as a slice.

Meaning, my pie chart has a slice for apple, banana, strawberry, apple, strawberry, and none.

This isn't what I'm going for. I did this by creating the dimension:

const dimension = crossfilter.dimension((customer: Customer) => {

    const productList = customer.orders.flatMap(x => x.products)
    const products = new Set<string>(productList);
    const result = [...products ];
    if (result.length === 0) return "none";

    return result;
})

const group = dimension.group();

The filtering behavior works correctly but the pie charts slices are not sustainable in this way.

EDIT: I added a js fiddle that demonstrates the situation a little more clearly: https://jsfiddle.net/qcaod71z/1/

2

There are 2 best solutions below

1
ninjagaijin On BEST ANSWER

https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension

You can now set the second argument specify that the return value is an array.

const dimension = crossfilter.dimension((customer: Customer) => {
    const productList = customer.orders.flatMap(x => x.products)
    const products = new Set<string>(productList);
    const result = [...products ];
    if (result.length === 0) return "none";

    return result;
}, true)
2
Amir MB On

It seems that's because you are returning an array, the result variable. You can extract products in a single list:

const customers = [
  { id: 1, orders: [{ products: ["apple", "banana"] }, { products: ["apple"] }] },
  { id: 2, orders: [{ products: ["strawberry"] }] },
  { id: 3, orders: [{ products: ["apple"] }, { products: ["strawberry"] }] },
  { id: 4, orders: [] }
];

const allProducts = [...customers.reduce((s, a) => {
  const customerProducts = a.orders.flatMap(o => o.products);
  customerProducts.forEach(p => s.add(p));
  if (customerProducts.length === 0)
    s.add("none");
  return s;
}, new Set())];

console.log(allProducts);

Or you can:

const customers = [
  { id: 1, orders: [{ products: ["apple", "banana"] }, { products: ["apple"] }] },
  { id: 2, orders: [{ products: ["strawberry"] }] },
  { id: 3, orders: [{ products: ["apple"] }, { products: ["strawberry"] }] },
  { id: 4, orders: [] }
]
const flattenedData = [];

customers.forEach(customer => {
    customer.orders.forEach(order => {
        order.products.forEach(product => {
            flattenedData.push({
                customerId: customer.id,
                product: product
            });
        });
    });
});

console.log(flattenedData);

And the setup crossfilter:

const crossfiltered = crossfilter(flattenedData);
const productDimension = crossfiltered.dimension(d => d.product);
const productGroup = productDimension.group();