2

Try to write a generic function function to return d3 scale. but getting the following error as it is reading the wrong type after the switch statement.

import * as D3Scale from 'd3-scale';

enum scaleIdentites {
linear,
time,
}

interface ScaleProps {
    scaleIdentity: scaleIdentites;
    range: number[];
    domain: number[];
}

export const scale = ({ scaleIdentity, domain }: ScaleProps) => {
    let scaleFunction: D3Scale.ScaleLinear<number, number> | D3Scale.ScaleTime<number, number>;
    switch (scaleIdentity) {
        case scaleIdentites.linear:
            scaleFunction = D3Scale.scaleLinear();
            scaleFunction.domain([1, 2]); // correctly reads the correct type and doesnt error.
            break;
        case scaleIdentites.time:
            scaleFunction = D3Scale.scaleTime();
            scaleFunction.domain([1, 2]); // correctly reads the correct type and doesnt error.
            break;
        default: {
            throw new Error(`Unknow scale ${scaleIdentity}`);
        }
    }
    if (domain) {
        scaleFunction.domain(domain); // error as saying should have 0 parameters.
    }
};

When inside the case block it correctly allows me to use a parameter in domain. Outside it errors.

9
  • maybe not related to the error but scaleTime uses Date as domain elements. Or are you interested in the first ms after 1970-01-01. You assign the result of a lambda that does not return a result. Commented Aug 2, 2018 at 10:44
  • @rioV8 thanks for the heads up. Tried changing to domain: Array<number | Date>; but still get the same error. It seems like typescript keeps linking back to domain(): number[] within the d3-scale types. When it should see it can have domain(domain: Array<number | { valueOf(): number }>): this; also. Ive basically had to duplicate code in the respective case statements Commented Aug 2, 2018 at 10:52
  • What does the generated JavaScript look like? Commented Aug 2, 2018 at 11:07
  • It wont compile. src/app/Components/D3/utils/scale.ts:55:5 - error TS2554: Expected 0 arguments, but got 1. Commented Aug 2, 2018 at 13:31
  • What is the code at line scale.ts::55? Commented Aug 2, 2018 at 13:45

1 Answer 1

2

The problem is that the 1-argument overloads of ScaleLinear.domain and ScaleTime.domain have different parameter types (even though number[] is assignable to both), and when you have a union type, TypeScript keeps only the call signatures with identical parameter types, which in this case is only the 0-argument overload.

In this example, it doesn't seem too bad to me to duplicate the if (domain) { scaleFunction.domain(domain); } logic in both cases. If you really want to avoid duplicating that if statement, you can do:

export const scale = ({ scaleIdentity, domain }: ScaleProps) => {
    let scaleFunction: D3Scale.ScaleLinear<number, number> | D3Scale.ScaleTime<number, number>;
    let setDomain: (domain: number[]) => void;
    switch (scaleIdentity) {
        case scaleIdentites.linear:
            const linearFunction = scaleFunction = D3Scale.scaleLinear();
            setDomain = (domain) => linearFunction.domain(domain);
            break;
        case scaleIdentites.time:
            const timeFunction = scaleFunction = D3Scale.scaleTime();
            setDomain = (domain) => timeFunction.domain(domain);
            break;
        default: {
            throw new Error(`Unknow scale ${scaleIdentity}`);
        }
    }
    if (domain) {
        setDomain(domain);
    }
};

Note the use of a new const variable because narrowing of let variables doesn't propagate into callbacks.

Typescript no compatible call signatures error with union types is very similar, but not similar enough for me to vote this as a duplicate.

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

1 Comment

thanks. I stuck with the duplicate if statements in the end but appreciate the explanation very much.

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.