I'm working on refactoring some existing code to be a bit more extensible and am wanting to create a generic factory API for starting D3 charts.

Currently, my factory looks like this:

import { select as d3Select } from 'd3';

const protoChart = {
  width: window.innerWidth,
  height: window.innerHeight,
  margin: {
    left: 10,
    right: 10,
    top: 10,
    bottom: 10,

export default function chartFactory(opts, proto = protoChart) {
  const chart = Object.assign({}, proto);
    .forEach(([key, val]) => {
      chart[key] = Object(val) === val ? Object.assign({}, chart[key], val) : val;

  chart.svg = d3Select('body')
    .attr('id', chart.id || 'chart')
    .attr('width', chart.width - chart.margin.right)
    .attr('height', chart.height - chart.margin.bottom);

  chart.container = chart.svg.append('g')
    .attr('id', 'container')
    .attr('transform', `translate(${chart.margin.left}, ${chart.margin.top})`);

  return chart;

Which I can then instantiate with:

const chart = chartFactory({
  margin: {
    left: 20,
  id: 'wooo',

This allows me to override the all the top-level properties with objects, meaning I can set the left margin and nothing else when I instantiate.

My questions:

  • Is there a better way of assigning my factory arguments to the prototype? I feel like I'm missing something here, though it could just be due to the fact I'm doing a semi-deep merge.

  • Am I instantiating the prototype within my factory correctly? Or will I have problems with instance safety if I create multiple charts?

  • Is there a better pattern I should be using for a functional programming-inspired library like D3?



Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.