Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing the D3 "reusable chart" pattern in TypeScript

The code in section 2 below (working example here) is based on the code in section 1 but changed to use arrow functions, and it is based on Mike Bostock's pattern in Toward Resusable Charts, namely returning a function that has other functions on it.

If I try to run either the code in section 1 or 2 in typescript (demo here) it says the methods addToChart and stop do not exist on type (selection: any) => () => void.

How can I get typescript to recognize the functions properties (addToChart and stop in this case) added to the returned function?

section 1

const mychart = function (){
  let stop = false;
  const chart = function(selection){
    function tick(){
      console.log("tick");
    }
    return tick;
  };

  // Adding a function to the returned 
  // function as in Bostock's reusable chart pattern
  chart.addToChart = function(value){ 
    console.log("addToChart");
    return chart;
  };

  chart.stop = function(){
    return stop = true;
  }

  return chart;
}

const a = mychart();
const tick = a();
tick(); //logs tick
a.addToChart(); //logs "addToChart"

section 2

const mychart = () => {
  let stop = false;

  const chart = (selection) => {
    function tick(){
      console.log("tick");
    }
    return tick;
  };

  chart.addToChart = (value) => {
    console.log("addToChart");
    return chart;
  };

  chart.stop = () => {
    return stop = true;
  }

  return chart;
} 

const a = mychart();
const tick = a();
tick(); //logs tick
a.addToChart(); //logs "addToChart"
like image 871
Leahcim Avatar asked Jun 29 '17 23:06

Leahcim


2 Answers

You can define a hybrid type, i.e. an interface describing both the function's signature as well as its properties. Given your code it could be something like this:

interface IChart {
    (selection: any): any;
    // Use overloading for D3 getter/setter pattern
    addToChart(): string;               // Getter
    addToChart(value: string): IChart;  // Setter
}

Since you should avoid any like the plague this might need some further refinement, but it should be enough to get you started. Furthermore, to allow for a D3-ish getter/setter pattern you can overload the addToChart function in the interface declaration.

Integrating this interface as a type in your reusable code pattern now becomes pretty straightforward:

const mychart = (): IChart => {

  // Private value exposed via closure
  let value: string|undefined;

  const chart = <IChart>((selection) => {
    // Private logic
  });

  // Public interface
  // Implementing a  D3-style getter/setter.
  chart.addToChart = function(val?: string): any {
    return arguments.length ? (value = val, chart) : value;
  };

  return chart;
} 

const chart = mychart();

console.log(chart.addToChart())   // --> undefined       
chart.addToChart("Add");          // Sets private value to "Add".
console.log(chart.addToChart())   // --> "Add"       

Have a look at the executable playground demo.

like image 101
altocumulus Avatar answered Oct 19 '22 12:10

altocumulus


I was wondering if you could use interface / class :

interface IChart {
    constructor: Function;
    addToChart?: (number) => Chart;
    stop: () => boolean;
}

class Chart implements IChart {

    private _stop = false;
    constructor( selection ) {
        // content of tick funciton here
    }

    public addToChart = function (n: number) {
        return this;
    }
    public stop = function () {
        return this._stop = true;
    }

}

let mychart = function () {
    let stop = false;
    let chartNew: Chart = new Chart(1);
    return chartNew;
}; 
like image 3
José Quinto Zamora Avatar answered Oct 19 '22 12:10

José Quinto Zamora