Deep understanding of d3js scales

Time:2021-2-21

https://www.cnblogs.com/kidsitcn/p/7182274.html

 

Deep understanding of d3js scales

 

The scale function is a JavaScript function like this:

  • Receive data input such as number, date, category, etc. and:
  • Returns a value representing a visual element, such as coordinates, color, length, or radius

Scale is usually used to transform (or map) abstract data values to visual quantization variables (such as position, length, color, etc.)

For example, suppose we have the following array data:

[ 0, 2, 3, 5, 7.5, 9, 10 ]

We can create a scale function like this:

var myScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 600]);

D3 will create a myscale function to receive the data input (domain) between [0,10] mapped to the position data (range) of [0600] pixels

We can use the myscale function to calculate the positions of the corresponding data

myScale(0);   // returns 0
myScale(2);   // returns 120
myScale(3);   // returns 180
...
myScale(10);  // returns 

As mentioned above, scale is mainly used to map abstract data into visual quantization elements, such as position, length, radius, color, etc. For example, they can apply it like this

  • Map abstract data to length values from 0 to 500 for use in bar chart
  • Map the abstract data to position data values between 0 and 200 for use as the coordinates of the midpoint of line charts
  • Map the percentage change data (+ 4%, + 10%, – 5%, etc.) to the corresponding change of color (for example, use red to represent positive value, the more positive, the redder, the negative, the more negative, the higher green saturation)
  • Map date data to positions on the x-axis

Constructing scales

In this part, we focus on the use of linear scale as an example to discuss the knowledge of scale, and we will discuss other types of scale later

var myScale = d3.scaleLinear();

 

Note: V4 and V3 are declared differently, D3. Scalelinear() in V4 and D3 scale.linear () in

myScale
  .domain([0, 100])
  .range([0, 800]);

With the above code, myscale becomes a scale function with a specific meaning. Now myscale can receive any domain between 0100 and map to a range from 0 to 800. We can call the scale function as follows:

myScale(0);    // returns 0
myScale(50);   // returns 400
myScale(100);  // returns 800

D3 scale types

D3 has about 12 different scale types (scalelinear, scalepow, scalequantise, scaleordinal, etc.), and generally speaking, we can divide them into three types

  • Scales with continuous input and continuous output
  • Scales with continuous input and discrete output
  • Scales with discrete input and discrete output

Now let’s study it one by one

Scales with continuous input and continuous output

scaleLinear

Linear scale is probably the most widely used type of scale, because they are most suitable for converting data into position and length. Therefore, we often take linear scale as an example to explain and learn the knowledge of scale

They use a linear function (y = m * x + b) to express the mathematical functional relationship between (domain) and (range)

var linearScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 600]);

linearScale(0);   // returns 0
linearScale(5);   // returns 300
linearScale(10);  // returns 600

Typically, they are used to transform abstract data into visual elements such as location, length, and so on. So when we create bar chart, line chart, or many other icon types, we can use it.

In addition to the position length as range, you can also use the color value (in fact, the color can also be used as the value of continuous data)

var linearScale = d3.scaleLinear()
  .domain([0, 10])
  .range(['yellow', 'red']);

linearScale(0);   // returns "rgb(255, 255, 0)"
linearScale(5);   // returns "rgb(255, 128, 0)"
linearScale(10);  // returns "rgb(255, 0, 0)"

This feature is often used in contour maps. Of course, we can also use scalequantize, scalequantile and scalethrshot

scalePow

Scalepow is used twiceY = m * x ^ k + B is a mathematical function to express the mathematical functional relationship between domain and range. The exponent K is set by. Exponent():

var powerScale = d3.scalePow() .exponent(0.5) .domain([0, 100]) .range([0, 30]); 
powerScale(0); // returns 0 
powerScale(50); // returns 21.21... 
powerScale(100); // returns 30

scaleSqrt

scaleSqrtScale power is a special form of scalepow (k = 0.5). It is usually used to represent a circle by area (instead of radius) (when the size of a circle is used to express the size of a data value, the best practice is to use an area equal to the size of the data instead of radius)

var sqrtScale = d3.scaleSqrt()
  .domain([0, 100])
  .range([0, 30]);

sqrtScale(0);   // returns 0
sqrtScale(50);  // returns 21.21...
sqrtScale(100); // returns 30

We can see that the output of this example is the same as that of the above example.

scaleLog

Log scales uses mathematical logarithmic functions(y = m * log(x) + b)If there is an exponential relationship between the data, this log scale is most suitable

var logScale = d3.scaleLog()
  .domain([10, 100000])
  .range([0, 600]);

logScale(10);     // returns 0
logScale(100);    // returns 150
logScale(1000);   // returns 300
logScale(100000); // returns 600

scaleTime

Scaletime and scalelinear are similar, the only difference is that domain is used to represent the array of date. (usually very useful for time series)

timeScale = d3.scaleTime() .domain([new Date(2016, 0, 1), new Date(2017, 0, 1)]) .range([0, 700]); 
timeScale(new Date(2016, 0, 1)); // returns 0 
timeScale(new Date(2016, 6, 1)); // returns 348.00... 
timeScale(new Date(2017, 0, 1)); // returns 700

scaleSequential

Scalesequencal is used to map continuous data to a range determined by a predefined or customized interpolation function

D3 provides many predefined interpolation functions, including many color related interpolation functions. For example, we can use D3. Interpolaterainbow to create a famous rainbow color scheme

var sequentialScale = d3.scaleSequential()
  .domain([0, 100])
  .interpolator(d3.interpolateRainbow);

sequentialScale(0);   // returns 'rgb(110, 64, 170)'
sequentialScale(50);  // returns 'rgb(175, 240, 91)'
sequentialScale(100); // returns 'rgb(110, 64, 170)'

Note that the interpolation function determines the output range, so you don’t need to specify the range for the sequential scale

The following columns show the other color interpolation range functions provided by D3:

var linearScale = d3.scaleLinear()
    .domain([0, 100])
    .range([0, 600]);

var sequentialScale = d3.scaleSequential()
    .domain([0, 100]);

var interpolators = [
    'interpolateViridis',
    'interpolateInferno',
    'interpolateMagma',
    'interpolatePlasma',
    'interpolateWarm',
    'interpolateCool',
    'interpolateRainbow',
    'interpolateCubehelixDefault'
];

var myData = d3.range(0, 100, 2);

function dots(d) {
    sequentialScale
        .interpolator(d3[d]);

    d3.select(this)
        .append('text')
        .attr('y', -10)
        .text(d);

    d3.select(this)
        .selectAll('rect')
        .data(myData)
        .enter()
        .append('rect')
        .attr('x', function(d) {
            return linearScale(d);
        })
        .attr('width', 11)
        .attr('height', 30)
        .style('fill', function(d) {
            return sequentialScale(d);
        });
}

d3.select('#wrapper')
    .selectAll('g.interpolator')
    .data(interpolators)
    .enter()
    .append('g')
    .classed('interpolator', true)
    .attr('transform', function(d, i) {
        return 'translate(0, ' + (i * 70) + ')';
    })
    .each(dots);

 

In addition to these color interpolation range functions defined by D3, there is also a “D3 scale chromic” plugin, which provides the famous “colorbrewer color schemes”

Clamping

By defaultscaleLinearscalePowscaleSqrtscaleLogscaleTime and scaleSequentialThe input value is allowed to be outside the domain range, for example:

var linearScale = d3.scaleLinear()

  .domain([0, 10])
  .range([0, 100]);

linearScale(20);  // returns 200
linearScale(-10); // returns -100

In this case, the scale function uses the extrapolation method to return the return value corresponding to the input value outside the domain.

If we want the scale function to strictly restrict the input value to be within the range specified by the domain, we can use the. Clamp() call

linearScale.clamp(true);

linearScale(20);  // returns 100
linearScale(-10); // returns 0

We can also close this function at any time through clamp (false)

Nice

If the domain is automatically calculated from the actual data, such as using D3. Extend, D3. Min / Max to define, then the start and end data may not be integers. This is not a problem in itself, but if you use this scale function to define a coordinate axis, it will be very untidy

var data = [0.243, 0.584, 0.987, 0.153, 0.433];
var extent = d3.extent(data);

var linearScale = d3.scaleLinear()
  .domain(extent)
  .range([0, 100]);

We use the. Nice() function to nice the domain

linearScale.nice();

Note that the. Nice() function must be called again every time after the domain is updated!

Multiple segments

The domain and range of scaleLinearscalePowscaleSqrtscaleLog and scaleTime usually consists of two values, but if we provide 3 or more values the scale function is subdivided into multiple segments:

Generally, the domain and range of scalelinear, scalepow, scalesqrt, scalelog and scaletime scales only contain two values: the start and end values. However, if we provide three or more values, the scale function will be divided into several segments

var linearScale = d3.scaleLinear()
  .domain([-10, 0, 10])
  .range(['red', '#ddd', 'blue']);

linearScale(-10);  // returns "rgb(255, 0, 0)"
linearScale(0);    // returns "rgb(221, 221, 221)"
linearScale(5);    // returns "rgb(128, 128, 255)"

Take a look at an example

var xScale = d3.scaleLinear()
    .domain([-10, 10])
    .range([0, 600]);

var linearScale = d3.scaleLinear()
    .domain([-10, 0, 10])
    .range(['red', '#ddd', 'blue']);

var myData = [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10];

d3.select('#wrapper')
    .selectAll('circle')
    .data(myData)
    .enter()
    .append('circle')
    .attr('r', 10)
    .attr('cx', function(d) {
        return xScale(d);
    })
    .style('fill', function(d) {
        return linearScale(d);
    });

Typically, a multi segment scale is used to distinguish between positive and negative values (as shown in the example above). As long as the number of segments of domain and range are the same, we can use any number of segments

Inversion

 .invert()Method accepts a range output and reverses the corresponding input domain

var linearScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 100]);

linearScale.invert(50);   // returns 5
linearScale.invert(100);  // returns 10

A common use case is when we want to convert a user’s click along an axis into a domain value:

The typical use scenario of this method is that we reverse the user’s clicking coordinates along a certain coordinate axis to the domain value

var width = 600;

var linearScale = d3.scaleLinear()
  .domain([-50, 50])
  .range([0, width])
  .nice();

var clickArea = d3.select('.click-area').node();

function doClick() {
    var pos = d3.mouse(clickArea);
    var xPos = pos[0];
    var value = linearScale.invert(xPos);
    d3.select('.info')
        .text('You clicked ' + value.toFixed(2));
}

// Construct axis
var axis = d3.axisBottom(linearScale);
d3.select('.axis')
    .call(axis);

// Update click area size
d3.select('.click-area')
    .attr('width', width)
    .attr('height', 40)
    .on('click', doClick);

Scales with continuous input and discrete output

scaleQuantize

scaleQuantizeAccept continuous range input and output discrete output defined by range

var quantizeScale = d3.scaleQuantize()
  .domain([0, 100])
  .range(['lightblue', 'orange', 'lightgreen', 'pink']);

quantizeScale(10);   // returns 'lightblue'
quantizeScale(30);  // returns 'orange'
quantizeScale(90);  // returns 'pink'
Each range value is mapped to an equal sized chunk in the domain so in the example above
  • 0 ≤ u < 25 is mapped to ‘lightblue’
  • 25 ≤ u < 50 is mapped to ‘orange’
  • 50 ≤ u < 75 is mapped to ‘lightgreen’
  • 75 ≤ u < 100 is mapped to ‘pink’

uIs the input domain value

Note that since we use the. Clamp directive, quantizescale (- 10) Returns’ lightblue ‘and quantizescale (110) Returns’ Pink’

scaleQuantile

scaleQuantileThe continuous domain value of the input is mapped to a discrete value. A domain is defined by an array

var myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100];

var quantileScale = d3.scaleQuantile()
  .domain(myData)
  .range(['lightblue', 'orange', 'lightgreen']);

quantileScale(0);   // returns 'lightblue'
quantileScale(20);  // returns 'lightblue'
quantileScale(30);  // returns 'orange'
quantileScale(65);  // returns 'lightgreen'

var myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100];

var linearScale = d3.scaleLinear()
    .domain([0, 100])
    .range([0, 600]);

var quantileScale = d3.scaleQuantile()
    .domain(myData)
    .range(['lightblue', 'orange', 'lightgreen']);

d3.select('#wrapper')
    .selectAll('circle')
    .data(myData)
    .enter()
    .append('circle')
    .attr('r', 3)
    .attr('cx', function(d) {
        return linearScale(d);
    })
    .style('fill', function(d) {
        return quantileScale(d);
    });

The ordered domain array is evenly divided into n sub ranges, where n is the number of range values

So in the example above, the domain array is divided into three groups equally

  • the first 5 values are mapped to ‘lightblue’
  • the next 5 values to ‘orange’ and
  • the last 5 values to ‘lightgreen’.

The specific domain average point can be accessed through. Quantities()

quantileScale.quantiles();  // returns [26.66..., 63]

If range contains four values, quantilescale calculates the quantities as follows: the lowest 25% of the data is mapped to range [0], the next 25% is mapped to range [1], and so on.

scaleThreshold

scaleThresholdIf there are N range values, there will be n-1 cut points

Here’s an example050 and100 segmentation

  • u < 0 is mapped to ‘#ccc’
  • 0 ≤ u < 50 to ‘lightblue’
  • 50 ≤ u < 100 to ‘orange’
  • u ≥ 100 to ‘#ccc’

Here uIt’s input value

var thresholdScale = d3.scaleThreshold()
  .domain([0, 50, 100])
  .range(['#ccc', 'lightblue', 'orange', '#ccc']);

thresholdScale(-10);  // returns '#ccc'
thresholdScale(20);   // returns 'lightblue'
thresholdScale(70);   // returns 'orange'
thresholdScale(110);  // returns '#ccc'

The detailed code is as follows:

var linearScale = d3.scaleLinear()
    .domain([-10, 110])
    .range([0, 600]);

var thresholdScale = d3.scaleThreshold()
    .domain([0, 50, 100])
    .range(['#ccc', 'lightblue', 'orange', '#ccc']);

var myData = d3.range(-10, 110, 2);

d3.select('#wrapper')
    .selectAll('rect')
    .data(myData)
    .enter()
    .append('rect')
    .attr('x', function(d) {
        return linearScale(d);
    })
    .attr('width', 9)
    .attr('height', 30)
    .style('fill', function(d) {
        return thresholdScale(d);
    });

Scales with discrete input and discrete output

scaleOrdinal

scaleOrdinalThe discrete domain values array is mapped to the discrete range values array. The domain input array specifies the possible input values, while the range array defines the corresponding possible output values. If the range array is shorter than the domain array, the range array will repeat the loop

var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(['black', '#ccc', '#ccc']);

ordinalScale('Jan');  // returns 'black';
ordinalScale('Feb');  // returns '#ccc';
ordinalScale('Mar');  // returns '#ccc';
ordinalScale('Apr');  // returns 'black';

The complete code is as follows:

var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var linearScale = d3.scaleLinear()
    .domain([0, 11])
    .range([0, 600]);

var ordinalScale = d3.scaleOrdinal()
    .domain(myData)
    .range(['black', '#ccc', '#ccc']);

d3.select('#wrapper')
    .selectAll('text')
    .data(myData)
    .enter()
    .append('text')
    .attr('x', function(d, i) {
        return linearScale(i);
    })
    .text(function(d) {
        return d;
    })
    .style('fill', function(d) {
        return ordinalScale(d);
    });

By default if a value that’s not in the domain is used as input, the scale will implicitly add the value to the domain:

By default, if the input value is not in the domain range, scale will implicitly add the value to the domain.

ordinalScale('Monday');  // returns 'black';

If this is not the behavior we want, we can use the. Unknown () function to set an unknown values

ordinalScale.unknown('Not a month');
ordinalScale('Tuesday'); // returns 'Not a month'

D3 also provides some predefined color schemes

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(d3.schemePaired);
var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var linearScale = d3.scaleLinear()
  .domain([0, 11])
  .range([0, 600]);

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(d3.schemePaired);

d3.select('#wrapper')
  .selectAll('text')
  .data(myData)
  .enter()
  .append('text')
  .attr('x', function(d, i) {
    return linearScale(i);
  })
  .text(function(d) {
    return d;
  })
  .style('fill', function(d) {
    return ordinalScale(d);
  });

(Note that the Brewer colour schemes are defined within a separate file d3-scale-chromatic.js.)

scaleBand

When creating a bar chart, scaleband can help us decide the geometry of the bar, and the padding value between the bars has been considered.

The input domain is specified by a numerical array (each value corresponds to a band), and the range is defined by the minimum and maximum ranges of the bands (that is, the entire width of the bar chart)

Scaleband will divide the range into n bands (n is the number of domain array) and calculate the position and width of each band considering padding

var bandScale = d3.scaleBand()
  .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
  .range([0, 200]);

bandScale('Mon'); // returns 0
bandScale('Tue'); // returns 40
bandScale('Fri'); // returns 160

The width of each band can be accessed using. Bandwidth().

bandScale.bandwidth();  // returns 40

There are two types of padding that can be configured:

  • paddingInner which specifies (as a percentage of the band width) the amount of padding between each band
  • paddingOuter which specifies (as a percentage of the band width) the amount of padding before the first band and after the last band

Let’s add a little inner padding to the above example

bandScale.paddingInner(0.05);

bandScale.bandWidth();  // returns 38.38...
bandScale('Mon');       // returns 0
bandScale('Tue');       // returns 40.40...

Putting this all together we can create this bar chart:

Add the above together and we can get the following chart:

var myData = [
    {day : 'Mon', value: 10},
    {day : 'Tue', value: 40},
    {day : 'Wed', value: 30},
    {day : 'Thu', value: 60},
    {day : 'Fri', value: 30}
];

var bandScale = d3.scaleBand()
    .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
    .range([0, 200])
    .paddingInner(0.05);

d3.select('#wrapper')
    .selectAll('rect')
    .data(myData)
    .enter()
    .append('rect')
    .attr('y', function(d) {
        return bandScale(d.day);
    })
    .attr('height', bandScale.bandwidth())
    .attr('width', function(d) {
        return d.value;
    });

 

scalePoint

scalePointThe discrete input values are mapped to equidistant points in the range

var pointScale = d3.scalePoint()
  .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
  .range([0, 500]);

pointScale('Mon');  // returns 0
pointScale('Tue');  // returns 125
pointScale('Fri');  // returns 500

Full code:

var myData = [
    {day : 'Mon', value: 10},
    {day : 'Tue', value: 40},
    {day : 'Wed', value: 30},
    {day : 'Thu', value: 60},
    {day : 'Fri', value: 30}
];

var pointScale = d3.scalePoint()
    .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
    .range([0, 600]);

d3.select('#wrapper')
    .selectAll('circle')
    .data(myData)
    .enter()
    .append('circle')
    .attr('cx', function(d) {
        return pointScale(d.day);
    })
    .attr('r', 4);

 

The distance between points can be accessed through. Step():

pointScale.step();  // returns 125

Outside padding can be specified by the ratio of padding to point spacing. For example, if you want to set outside padding to 1 / 4 of point spacing, you can set it as follows:

pointScale.padding(0.25);

pointScale('Mon');  // returns 27.77...
pointScale.step();  // returns 111.11...

 

Reference reading

ColorBrewer schemes for D3

Mike Bostock on d3-scale

,

The scale function is a JavaScript function like this:

  • Receive data input such as number, date, category, etc. and:
  • Returns a value representing a visual element, such as coordinates, color, length, or radius

Scale is usually used to transform (or map) abstract data values to visual quantization variables (such as position, length, color, etc.)

For example, suppose we have the following array data:

[ 0, 2, 3, 5, 7.5, 9, 10 ]

We can create a scale function like this:

var myScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 600]);

D3 will create a myscale function to receive the data input (domain) between [0,10] mapped to the position data (range) of [0600] pixels

We can use the myscale function to calculate the positions of the corresponding data

myScale(0);   // returns 0
myScale(2);   // returns 120
myScale(3);   // returns 180
...
myScale(10);  // returns 

As mentioned above, scale is mainly used to map abstract data into visual quantization elements, such as position, length, radius, color, etc. For example, they can apply it like this

  • Map abstract data to length values from 0 to 500 for use in bar chart
  • Map the abstract data to position data values between 0 and 200 for use as the coordinates of the midpoint of line charts
  • Map the percentage change data (+ 4%, + 10%, – 5%, etc.) to the corresponding change of color (for example, use red to represent positive value, the more positive, the redder, the negative, the more negative, the higher green saturation)
  • Map date data to positions on the x-axis

Constructing scales

In this part, we focus on the use of linear scale as an example to discuss the knowledge of scale, and we will discuss other types of scale later

var myScale = d3.scaleLinear();

 

Note: V4 and V3 are declared differently, D3. Scalelinear() in V4 and D3 scale.linear () in

myScale
  .domain([0, 100])
  .range([0, 800]);

With the above code, myscale becomes a scale function with a specific meaning. Now myscale can receive any domain between 0100 and map to a range from 0 to 800. We can call the scale function as follows:

myScale(0);    // returns 0
myScale(50);   // returns 400
myScale(100);  // returns 800

D3 scale types

D3 has about 12 different scale types (scalelinear, scalepow, scalequantise, scaleordinal, etc.), and generally speaking, we can divide them into three types

  • Scales with continuous input and continuous output
  • Scales with continuous input and discrete output
  • Scales with discrete input and discrete output

Now let’s study it one by one

Scales with continuous input and continuous output

scaleLinear

Linear scale is probably the most widely used type of scale, because they are most suitable for converting data into position and length. Therefore, we often take linear scale as an example to explain and learn the knowledge of scale

They use a linear function (y = m * x + b) to express the mathematical functional relationship between (domain) and (range)

var linearScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 600]);

linearScale(0);   // returns 0
linearScale(5);   // returns 300
linearScale(10);  // returns 600

Typically, they are used to transform abstract data into visual elements such as location, length, and so on. So when we create bar chart, line chart, or many other icon types, we can use it.

In addition to the position length as range, you can also use the color value (in fact, the color can also be used as the value of continuous data)

var linearScale = d3.scaleLinear()
  .domain([0, 10])
  .range(['yellow', 'red']);

linearScale(0);   // returns "rgb(255, 255, 0)"
linearScale(5);   // returns "rgb(255, 128, 0)"
linearScale(10);  // returns "rgb(255, 0, 0)"

This feature is often used in contour maps. Of course, we can also use scalequantize, scalequantile and scalethrshot

scalePow

Scalepow is used twiceY = m * x ^ k + B is a mathematical function to express the mathematical functional relationship between domain and range. The exponent K is set by. Exponent():

var powerScale = d3.scalePow() .exponent(0.5) .domain([0, 100]) .range([0, 30]); 
powerScale(0); // returns 0 
powerScale(50); // returns 21.21... 
powerScale(100); // returns 30

scaleSqrt

scaleSqrtScale power is a special form of scalepow (k = 0.5). It is usually used to represent a circle by area (instead of radius) (when the size of a circle is used to express the size of a data value, the best practice is to use an area equal to the size of the data instead of radius)

var sqrtScale = d3.scaleSqrt()
  .domain([0, 100])
  .range([0, 30]);

sqrtScale(0);   // returns 0
sqrtScale(50);  // returns 21.21...
sqrtScale(100); // returns 30

We can see that the output of this example is the same as that of the above example.

scaleLog

Log scales uses mathematical logarithmic functions(y = m * log(x) + b)If there is an exponential relationship between the data, this log scale is most suitable

var logScale = d3.scaleLog()
  .domain([10, 100000])
  .range([0, 600]);

logScale(10);     // returns 0
logScale(100);    // returns 150
logScale(1000);   // returns 300
logScale(100000); // returns 600

scaleTime

Scaletime and scalelinear are similar, the only difference is that domain is used to represent the array of date. (usually very useful for time series)

timeScale = d3.scaleTime() .domain([new Date(2016, 0, 1), new Date(2017, 0, 1)]) .range([0, 700]); 
timeScale(new Date(2016, 0, 1)); // returns 0 
timeScale(new Date(2016, 6, 1)); // returns 348.00... 
timeScale(new Date(2017, 0, 1)); // returns 700

scaleSequential

Scalesequencal is used to map continuous data to a range determined by a predefined or customized interpolation function

D3 provides many predefined interpolation functions, including many color related interpolation functions. For example, we can use D3. Interpolaterainbow to create a famous rainbow color scheme

var sequentialScale = d3.scaleSequential()
  .domain([0, 100])
  .interpolator(d3.interpolateRainbow);

sequentialScale(0);   // returns 'rgb(110, 64, 170)'
sequentialScale(50);  // returns 'rgb(175, 240, 91)'
sequentialScale(100); // returns 'rgb(110, 64, 170)'

Note that the interpolation function determines the output range, so you don’t need to specify the range for the sequential scale

The following columns show the other color interpolation range functions provided by D3:

var linearScale = d3.scaleLinear()
    .domain([0, 100])
    .range([0, 600]);

var sequentialScale = d3.scaleSequential()
    .domain([0, 100]);

var interpolators = [
    'interpolateViridis',
    'interpolateInferno',
    'interpolateMagma',
    'interpolatePlasma',
    'interpolateWarm',
    'interpolateCool',
    'interpolateRainbow',
    'interpolateCubehelixDefault'
];

var myData = d3.range(0, 100, 2);

function dots(d) {
    sequentialScale
        .interpolator(d3[d]);

    d3.select(this)
        .append('text')
        .attr('y', -10)
        .text(d);

    d3.select(this)
        .selectAll('rect')
        .data(myData)
        .enter()
        .append('rect')
        .attr('x', function(d) {
            return linearScale(d);
        })
        .attr('width', 11)
        .attr('height', 30)
        .style('fill', function(d) {
            return sequentialScale(d);
        });
}

d3.select('#wrapper')
    .selectAll('g.interpolator')
    .data(interpolators)
    .enter()
    .append('g')
    .classed('interpolator', true)
    .attr('transform', function(d, i) {
        return 'translate(0, ' + (i * 70) + ')';
    })
    .each(dots);

 

In addition to these color interpolation range functions defined by D3, there is also a “D3 scale chromic” plugin, which provides the famous “colorbrewer color schemes”

Clamping

By defaultscaleLinearscalePowscaleSqrtscaleLogscaleTime and scaleSequentialThe input value is allowed to be outside the domain range, for example:

var linearScale = d3.scaleLinear()

  .domain([0, 10])
  .range([0, 100]);

linearScale(20);  // returns 200
linearScale(-10); // returns -100

In this case, the scale function uses the extrapolation method to return the return value corresponding to the input value outside the domain.

If we want the scale function to strictly restrict the input value to be within the range specified by the domain, we can use the. Clamp() call

linearScale.clamp(true);

linearScale(20);  // returns 100
linearScale(-10); // returns 0

We can also close this function at any time through clamp (false)

Nice

If the domain is automatically calculated from the actual data, such as using D3. Extend, D3. Min / Max to define, then the start and end data may not be integers. This is not a problem in itself, but if you use this scale function to define a coordinate axis, it will be very untidy

var data = [0.243, 0.584, 0.987, 0.153, 0.433];
var extent = d3.extent(data);

var linearScale = d3.scaleLinear()
  .domain(extent)
  .range([0, 100]);

We use the. Nice() function to nice the domain

linearScale.nice();

Note that the. Nice() function must be called again every time after the domain is updated!

Multiple segments

The domain and range of scaleLinearscalePowscaleSqrtscaleLog and scaleTime usually consists of two values, but if we provide 3 or more values the scale function is subdivided into multiple segments:

Generally, the domain and range of scalelinear, scalepow, scalesqrt, scalelog and scaletime scales only contain two values: the start and end values. However, if we provide three or more values, the scale function will be divided into several segments

var linearScale = d3.scaleLinear()
  .domain([-10, 0, 10])
  .range(['red', '#ddd', 'blue']);

linearScale(-10);  // returns "rgb(255, 0, 0)"
linearScale(0);    // returns "rgb(221, 221, 221)"
linearScale(5);    // returns "rgb(128, 128, 255)"

Take a look at an example

var xScale = d3.scaleLinear()
    .domain([-10, 10])
    .range([0, 600]);

var linearScale = d3.scaleLinear()
    .domain([-10, 0, 10])
    .range(['red', '#ddd', 'blue']);

var myData = [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10];

d3.select('#wrapper')
    .selectAll('circle')
    .data(myData)
    .enter()
    .append('circle')
    .attr('r', 10)
    .attr('cx', function(d) {
        return xScale(d);
    })
    .style('fill', function(d) {
        return linearScale(d);
    });

Typically, a multi segment scale is used to distinguish between positive and negative values (as shown in the example above). As long as the number of segments of domain and range are the same, we can use any number of segments

Inversion

 .invert()Method accepts a range output and reverses the corresponding input domain

var linearScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 100]);

linearScale.invert(50);   // returns 5
linearScale.invert(100);  // returns 10

A common use case is when we want to convert a user’s click along an axis into a domain value:

The typical use scenario of this method is that we reverse the user’s clicking coordinates along a certain coordinate axis to the domain value

var width = 600;

var linearScale = d3.scaleLinear()
  .domain([-50, 50])
  .range([0, width])
  .nice();

var clickArea = d3.select('.click-area').node();

function doClick() {
    var pos = d3.mouse(clickArea);
    var xPos = pos[0];
    var value = linearScale.invert(xPos);
    d3.select('.info')
        .text('You clicked ' + value.toFixed(2));
}

// Construct axis
var axis = d3.axisBottom(linearScale);
d3.select('.axis')
    .call(axis);

// Update click area size
d3.select('.click-area')
    .attr('width', width)
    .attr('height', 40)
    .on('click', doClick);

Scales with continuous input and discrete output

scaleQuantize

scaleQuantizeAccept continuous range input and output discrete output defined by range

var quantizeScale = d3.scaleQuantize()
  .domain([0, 100])
  .range(['lightblue', 'orange', 'lightgreen', 'pink']);

quantizeScale(10);   // returns 'lightblue'
quantizeScale(30);  // returns 'orange'
quantizeScale(90);  // returns 'pink'
Each range value is mapped to an equal sized chunk in the domain so in the example above
  • 0 ≤ u < 25 is mapped to ‘lightblue’
  • 25 ≤ u < 50 is mapped to ‘orange’
  • 50 ≤ u < 75 is mapped to ‘lightgreen’
  • 75 ≤ u < 100 is mapped to ‘pink’

uIs the input domain value

Note that since we use the. Clamp directive, quantizescale (- 10) Returns’ lightblue ‘and quantizescale (110) Returns’ Pink’

scaleQuantile

scaleQuantileThe continuous domain value of the input is mapped to a discrete value. A domain is defined by an array

var myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100];

var quantileScale = d3.scaleQuantile()
  .domain(myData)
  .range(['lightblue', 'orange', 'lightgreen']);

quantileScale(0);   // returns 'lightblue'
quantileScale(20);  // returns 'lightblue'
quantileScale(30);  // returns 'orange'
quantileScale(65);  // returns 'lightgreen'

var myData = [0, 5, 7, 10, 20, 30, 35, 40, 60, 62, 65, 70, 80, 90, 100];

var linearScale = d3.scaleLinear()
    .domain([0, 100])
    .range([0, 600]);

var quantileScale = d3.scaleQuantile()
    .domain(myData)
    .range(['lightblue', 'orange', 'lightgreen']);

d3.select('#wrapper')
    .selectAll('circle')
    .data(myData)
    .enter()
    .append('circle')
    .attr('r', 3)
    .attr('cx', function(d) {
        return linearScale(d);
    })
    .style('fill', function(d) {
        return quantileScale(d);
    });

The ordered domain array is evenly divided into n sub ranges, where n is the number of range values

So in the example above, the domain array is divided into three groups equally

  • the first 5 values are mapped to ‘lightblue’
  • the next 5 values to ‘orange’ and
  • the last 5 values to ‘lightgreen’.

The specific domain average point can be accessed through. Quantities()

quantileScale.quantiles();  // returns [26.66..., 63]

If range contains four values, quantilescale calculates the quantities as follows: the lowest 25% of the data is mapped to range [0], the next 25% is mapped to range [1], and so on.

scaleThreshold

scaleThresholdIf there are N range values, there will be n-1 cut points

Here’s an example050 and100 segmentation

  • u < 0 is mapped to ‘#ccc’
  • 0 ≤ u < 50 to ‘lightblue’
  • 50 ≤ u < 100 to ‘orange’
  • u ≥ 100 to ‘#ccc’

Here uIt’s input value

var thresholdScale = d3.scaleThreshold()
  .domain([0, 50, 100])
  .range(['#ccc', 'lightblue', 'orange', '#ccc']);

thresholdScale(-10);  // returns '#ccc'
thresholdScale(20);   // returns 'lightblue'
thresholdScale(70);   // returns 'orange'
thresholdScale(110);  // returns '#ccc'

The detailed code is as follows:

var linearScale = d3.scaleLinear()
    .domain([-10, 110])
    .range([0, 600]);

var thresholdScale = d3.scaleThreshold()
    .domain([0, 50, 100])
    .range(['#ccc', 'lightblue', 'orange', '#ccc']);

var myData = d3.range(-10, 110, 2);

d3.select('#wrapper')
    .selectAll('rect')
    .data(myData)
    .enter()
    .append('rect')
    .attr('x', function(d) {
        return linearScale(d);
    })
    .attr('width', 9)
    .attr('height', 30)
    .style('fill', function(d) {
        return thresholdScale(d);
    });

Scales with discrete input and discrete output

scaleOrdinal

scaleOrdinalThe discrete domain values array is mapped to the discrete range values array. The domain input array specifies the possible input values, while the range array defines the corresponding possible output values. If the range array is shorter than the domain array, the range array will repeat the loop

var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(['black', '#ccc', '#ccc']);

ordinalScale('Jan');  // returns 'black';
ordinalScale('Feb');  // returns '#ccc';
ordinalScale('Mar');  // returns '#ccc';
ordinalScale('Apr');  // returns 'black';

The complete code is as follows:

var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var linearScale = d3.scaleLinear()
    .domain([0, 11])
    .range([0, 600]);

var ordinalScale = d3.scaleOrdinal()
    .domain(myData)
    .range(['black', '#ccc', '#ccc']);

d3.select('#wrapper')
    .selectAll('text')
    .data(myData)
    .enter()
    .append('text')
    .attr('x', function(d, i) {
        return linearScale(i);
    })
    .text(function(d) {
        return d;
    })
    .style('fill', function(d) {
        return ordinalScale(d);
    });

By default if a value that’s not in the domain is used as input, the scale will implicitly add the value to the domain:

By default, if the input value is not in the domain range, scale will implicitly add the value to the domain.

ordinalScale('Monday');  // returns 'black';

If this is not the behavior we want, we can use the. Unknown () function to set an unknown values

ordinalScale.unknown('Not a month');
ordinalScale('Tuesday'); // returns 'Not a month'

D3 also provides some predefined color schemes

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(d3.schemePaired);
var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var linearScale = d3.scaleLinear()
  .domain([0, 11])
  .range([0, 600]);

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(d3.schemePaired);

d3.select('#wrapper')
  .selectAll('text')
  .data(myData)
  .enter()
  .append('text')
  .attr('x', function(d, i) {
    return linearScale(i);
  })
  .text(function(d) {
    return d;
  })
  .style('fill', function(d) {
    return ordinalScale(d);
  });

(Note that the Brewer colour schemes are defined within a separate file d3-scale-chromatic.js.)

scaleBand

When creating a bar chart, scaleband can help us decide the geometry of the bar, and the padding value between the bars has been considered.

The input domain is specified by a numerical array (each value corresponds to a band), and the range is defined by the minimum and maximum ranges of the bands (that is, the entire width of the bar chart)

Scaleband will divide the range into n bands (n is the number of domain array) and calculate the position and width of each band considering padding

var bandScale = d3.scaleBand()
  .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
  .range([0, 200]);

bandScale('Mon'); // returns 0
bandScale('Tue'); // returns 40
bandScale('Fri'); // returns 160

The width of each band can be accessed using. Bandwidth().

bandScale.bandwidth();  // returns 40

There are two types of padding that can be configured:

  • paddingInner which specifies (as a percentage of the band width) the amount of padding between each band
  • paddingOuter which specifies (as a percentage of the band width) the amount of padding before the first band and after the last band

Let’s add a little inner padding to the above example

bandScale.paddingInner(0.05);

bandScale.bandWidth();  // returns 38.38...
bandScale('Mon');       // returns 0
bandScale('Tue');       // returns 40.40...

Putting this all together we can create this bar chart:

Add the above together and we can get the following chart:

var myData = [
    {day : 'Mon', value: 10},
    {day : 'Tue', value: 40},
    {day : 'Wed', value: 30},
    {day : 'Thu', value: 60},
    {day : 'Fri', value: 30}
];

var bandScale = d3.scaleBand()
    .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
    .range([0, 200])
    .paddingInner(0.05);

d3.select('#wrapper')
    .selectAll('rect')
    .data(myData)
    .enter()
    .append('rect')
    .attr('y', function(d) {
        return bandScale(d.day);
    })
    .attr('height', bandScale.bandwidth())
    .attr('width', function(d) {
        return d.value;
    });

 

scalePoint

scalePointThe discrete input values are mapped to equidistant points in the range

var pointScale = d3.scalePoint()
  .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
  .range([0, 500]);

pointScale('Mon');  // returns 0
pointScale('Tue');  // returns 125
pointScale('Fri');  // returns 500

Full code:

var myData = [
    {day : 'Mon', value: 10},
    {day : 'Tue', value: 40},
    {day : 'Wed', value: 30},
    {day : 'Thu', value: 60},
    {day : 'Fri', value: 30}
];

var pointScale = d3.scalePoint()
    .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
    .range([0, 600]);

d3.select('#wrapper')
    .selectAll('circle')
    .data(myData)
    .enter()
    .append('circle')
    .attr('cx', function(d) {
        return pointScale(d.day);
    })
    .attr('r', 4);

 

The distance between points can be accessed through. Step():

pointScale.step();  // returns 125

Outside padding can be specified by the ratio of padding to point spacing. For example, if you want to set outside padding to 1 / 4 of point spacing, you can set it as follows:

pointScale.padding(0.25);

pointScale('Mon');  // returns 27.77...
pointScale.step();  // returns 111.11...

 

Reference reading

ColorBrewer schemes for D3

Mike Bostock on d3-scale