0

I have the following code that I am trying to make strongly typed so that it is easier to maintain.

However for the menu variable I get the following error:

[ts]
Type '(x: number, y: number) => void' is not assignable to type 'ContextMenu'.
  Property 'items' is missing in type '(x: number, y: number) => void'.



import * as d3 from "d3";
import "d3-selection-multi";

interface ContextMenu {
    (x: number, y: number) : void;
    items(items?: string[]): string[] | this;
    remove(): void;
}

export function contextMenu(): ContextMenu {
    var height,
        width, 
        margin = 0.1, // fraction of width
        items = [], 
        rescale: boolean = false, 
        style = {
            'rect': {
                'mouseout': {
                    "fill": 'rgb(244,244,244)', 
                    "stroke": 'white', 
                    "strokeWidth": '1px'
                }, 
                'mouseover': {
                    "fill": 'rgb(200,200,200)'
                }
            }, 
            'text': {
                'fill': 'steelblue', 
                'font-size': '13'
            }
        }; 

    var menu: ContextMenu = function (x:number, y:number) {
        menu.remove();
        scaleItems();

        // Draw the menu
        d3.selectAll('svg.chart')
            .append('g').attr('class', 'context-menu')
            .selectAll('tmp')
            .data(items).enter()
            .append('g').attr('class', 'menu-entry')
            .style('cursor', 'pointer')
            .on('mouseover', function() {
                d3.select(this).select('rect').styles((<any>style).rect.mouseover) })
            .on('mouseout', function() {
                d3.select(this).select('rect').styles((<any>style).rect.mouseout) });

        d3.selectAll('.menu-entry')
            .append('rect')
            .attr('x', x)
            .attr('y', (d, i) => y + (i * height))
            .attr('width', width)
            .attr('height', height)
            .styles((<any>style).rect.mouseout);

        d3.selectAll('.menu-entry')
            .append('text')
            .text((d: string) => d)
            .attr('x', x)
            .attr('y', (d, i) => y + (i * height))
            .attr('dy', height - margin / 2)
            .attr('dx', margin)
            .styles((<any>style).text);

        // Other interactions
        d3.select('body')
            .on('click', function() {
                d3.select('.context-menu').remove();
            });
    }

    menu.remove = function() {
        d3.selectAll(".context-menu").remove();
    };

    menu.items = function(_?) {
        return (!arguments.length) 
        ? items 
        :(items = _, rescale = true, menu);
    }

    // Automatically set width, height, and margin;
    function scaleItems() {
        if (!rescale) {
            return;
        }
        d3.selectAll('svg').selectAll('tmp')
            .data(items).enter()
            .append('text')
            .text(d => d)
            .styles(<any>style.text)
            .attr('x', -1000)
            .attr('y', -1000)
            .attr('class', 'tmp');

        var z = d3.selectAll('.tmp')
            .nodes()
            .map((x:any) => x.getBBox());

        width = d3.max(z.map(x => x.width));
        margin = margin * width;
        width =  width + 2 * margin;
        height = d3.max(z.map(x => x.height + margin / 2 ));

        // cleanup
        d3.selectAll('.tmp').remove();
        rescale = false;
    }
    return menu;
}

Howe can I make the code compile but keep the same code style idiomatic D3?

1 Answer 1

1

Sadly, there is no idiomatic way to extend function in your case. The only fallback is to cast menu function to any.

var menu: ContextMenu = function (x:number, y:number) {
    // ....
} as any

TypeScript has another functionality calls "namespace merging" to accommodate extending function literal.

function menu () {}
namespace menu {
  export function remove() {}
}
menu.remove() // compiles

However, namespace can only appears on top level of a module or nested in another namespace. You cannot declare it in a function closure. So in this case you have to fallback to any, any way.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.