Fake log scale for negative values?

2,256 views
Skip to first unread message

Dan

unread,
Dec 16, 2011, 10:53:57 AM12/16/11
to d3-js
1. What would be a clean way of "faking a log scale for negative
values"?

The use case:
I'm using the d3.svg.axis() and would like to show a
domain([-100,100]) on this pseudo-log scale so that -100 has the same
distance to 0 as 100.

2. Log scale ticks?

Would the code in this pull request be compatible with (1) or would I
have to adapt it to account for the negative values?
https://github.com/mbostock/d3/pull/308

Thanks :)

Dan

unread,
Dec 18, 2011, 6:40:28 AM12/18/11
to d3-js
It seems d3_scale_logn is able to handle a negative domain, i.e.
[-5,-1]:

function d3_scale_logn(x) {
return -Math.log(-x) / Math.LN10;
}

But for the axis to make sense, I'd like to show both positive and
negative values on a log scale, i.e. the domain [-5,100]
The desired effect is something like this: http://i.imgur.com/uaVEc.png

Do I have to break the scale in two?

Negative values: domain([-5,-0.1?])
Positive values: domain([0.1?,100])

And how would I handle real 0 values?

Thanks!

Dan

unread,
Dec 19, 2011, 2:51:10 PM12/19/11
to d3-js
It seems d3_scale_logn can bes used for negative domains?

function d3_scale_logn(x) {
return -Math.log(-x) / Math.LN10;
}

But how could I achieve something like this - http://i.imgur.com/nnyNZ.png
- using the axes generator?

Do I have to break the scale in two?

[-1,-0.1?], [0.1,1]

Thanks!

On Dec 16, 4:53 pm, Dan <dan...@lapidus.se> wrote:

Dan

unread,
Dec 17, 2011, 11:33:21 AM12/17/11
to d3-js
OK, the d3_scale_logn function seems to return what I'm looking for:

function d3_scale_logn(x) {
return -Math.log(-x) / Math.LN10;
}

But suppose I'd like to create a log scale with the domain [-5,100].
1) Should I divide this into two domains? [-5,-0.1?] and [0.1?,100]
2) And how can I use two scales with the axes generator?

Thanks!


On Dec 16, 4:53 pm, Dan <dan...@lapidus.se> wrote:

> 1. What would be a clean way of "faking alogscale fornegative
> values"?
>
> The use case:
> I'm using the d3.svg.axis() and would like to show a

> domain([-100,100]) on this pseudo-logscale so that -100 has the same


> distance to 0 as 100.
>

> 2.Logscale ticks?

Mike Bostock

unread,
Dec 20, 2011, 11:47:14 AM12/20/11
to d3...@googlegroups.com
> But for the axis to make sense, I'd like to show both positive and
> negative values on a log scale, i.e. the domain [-5,100]

That's not possible with a log scale, because zero is at infinity.
Your image would look like this:

1%
0.1%
0.01%
0.001%
0.0001%
0.00001%
0.000001%
0.0000001%
… infinity passes
-0.0000001%
-0.000001%
-0.00001%
-0.0001%
-0.001%
-0.01%
-0.1%
-1%

You might be able to approximate this with a polylog domain that is
undefined in the intermediate region, i.e., has a hard break from
[-0.1%,0.1%]. That would look like this:

d3.scale.log()
.domain([-5.0, -0.1, 0.1, 100.0])
.range([-200, 0, 0, 200]);

But, that's kind of abusing the scale; if you do this, you should draw
a discontinuity on the axis for clarity.

If you have negative and positive values, you probably want to use a
sqrt scale (or more generally, a pow scale) instead. It handles this
automatically.

Mike

Sándor Rácz

unread,
Nov 3, 2012, 6:34:03 AM11/3/12
to d3...@googlegroups.com, mbos...@cs.stanford.edu

I have the same issue.
Trying to figure out a solution to this, and already have an idea, bit I'm not sure if this works:
What if we create a scale by combining a linear and a log scale. The linear scale would make a projection from the desired domain to the range [1,10] (this really doesn't matter..any range with positive boundaries would do)  then the log scale would project this range - now as a domain - to the desired range.

Any thoughts? Or is it a bad idea?

//Sandor

Sándor Rácz

unread,
Nov 5, 2012, 4:00:52 AM11/5/12
to d3...@googlegroups.com, mbos...@cs.stanford.edu
OK. It works like charm.
Here's the code if anyone runs into this issue:

    d3.scale.genericLog = function() {
        return GenericLog();
    };
    function GenericLog() {
        var PROJECTION=[1,10];
        var linearScale, logScale;
        
        linearScale=d3.scale.linear();
        linearScale.range(PROJECTION);
        
        logScale=d3.scale.log();
        logScale.domain(PROJECTION);
        
        function scale(x) {
            return logScale(linearScale(x));
        }
        scale.domain = function(x) {
            if (!arguments.length) return linearScale.domain();
            linearScale.domain(x);
            return scale;
        };
        scale.range = function(x) {
            if (!arguments.length) return logScale.range();
            logScale.range(x);
            return scale;
        };
        scale.ticks = function(m) {
            return linearScale.ticks(m);
        };
        return scale;

    }

Cheers,
//Sandor

Mike Bostock

unread,
Nov 5, 2012, 10:01:47 AM11/5/12
to d3...@googlegroups.com
> Or is it a bad idea?

This sounds like a bad idea in that it introduces distortion. Maybe a
d3.scale.sqrt would be better, or a visible discontinuity in the axis?

Mike

Sándor Rácz

unread,
Nov 5, 2012, 3:35:01 PM11/5/12
to d3...@googlegroups.com
Thanks for the comments,

I agree this does introduce a distortion depending on the what you choose as PROJECTION interval.
However in my case the customer's explicit request was to use log scale to stretch out the lower part of the axis. (Only the lower part, not around 0. This is very specific for the data being visualized)

d3.scale.sqrt would be good for domains like [0,n] but for domains like [500 000,600 000] it doesn't stretch the axis enough.

I think the overall best option is the discontinuity you mentioned, but for this specific case i'll go with the distorted scale, as it suits my needs the best.

Thanks,
//Sandor

Adam Wexler

unread,
Aug 24, 2013, 8:48:42 PM8/24/13
to d3...@googlegroups.com, mbos...@cs.stanford.edu
You cannot have a domain in which the values cross zero, even in a polylog scale. See https://github.com/mbostock/d3/issues/1420#issuecomment-21500319
Message has been deleted
Message has been deleted
Message has been deleted

Sándor Rácz

unread,
Jan 26, 2016, 1:21:17 PM1/26/16
to d3...@googlegroups.com

Good one, but there's a typo:
var naturalScale= d3.scale.linear()

   .domain([salesRange[0],salesRange[1]])

   .range([0,totalSalesRange]);

2016. jan. 26. 17:15 ezt írta ("Youngsoo Yi" <bara...@gmail.com>):


This sounds like a bad idea in that it introduces distortion. Maybe a 
d3.scale.sqrt would be better, or a visible discontinuity in the axis? 

Mike 

here's my solution.

-----------

 var salesRange = ([-20, 100])

// make range that log could take.
var totalSalesRange = salesRange[1]-salesRange[0];

// real number to natural number scale 
var naturalScale= d3.scale.linear()
   .domain([salesRange[1],salesRange[1]])
   .range([0,totalSalesRange]);

// log sale. takes natural domain.
var rScale = d3.scale.sqrt()
   .range([0, maxRadius]).
   rScale.domain([0,totalSalesRange]);;

// process sequencialy
 data.forEach(function (item, index) {
    var naturalSales = naturalScale(item.sales);
    item.r = rScale(naturalSales);

-----------

if you want to get negative value?
just minus negative range. you'll get it.

-----------
item.r = rScale(naturalSales) - rScale(-salesRange[0]);
-----------

--
You received this message because you are subscribed to a topic in the Google Groups "d3-js" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/d3-js/QCOk9Or7vWI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Youngsoo Yi

unread,
Jan 27, 2016, 7:45:32 PM1/27/16
to d3-js
Thanks! I got It.

Here's another way.


var totalSalesRange = salesRange[1]-salesRange[0];

var rScale = d3.scale.sqrt()
    .domain([0,totalSalesRange]);   
    .range([0, maxRadius]);

var scale = rScale(d.sales) - rScale(-salesRange[0]);

Xavier Plantaz

unread,
Mar 31, 2016, 6:03:29 PM3/31/16
to d3-js
That's might not be the cleaner way but I think that I found a walkaround.

http://stackoverflow.com/questions/36329468/d3js-create-custom-scale-positive-and-negative-logarithm

Here is the associated JSFiddle
Reply all
Reply to author
Forward
0 new messages