-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathd3.scale.genericLog.js
157 lines (122 loc) · 4.81 KB
/
d3.scale.genericLog.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
A log scale that can handle positive and negative values on the same graph.
Not really an honest scale - it has a disconuity.
linearPartDistance is the distance below and above the zero point at which
the scale switches from being logarithmic to being linear.
*/
d3.scale.genericLog = function(linearPartDistance) {
return GenericLog(linearPartDistance);
};
function GenericLog(d) {
if (d == undefined) {
d = 10;
}
var linearPartDistance = 10;
var logScale = d3.scale.log();
var linScale = d3.scale.linear();
// Overall range/domain
var domain = [-10, 10];
var range = [0, 100];
var zeroPoint = 50;
linScale.domain([-linearPartDistance, +linearPartDistance]);
function scale(x) {
if (x >= linScale.domain()[1]) return zeroPoint + logScale(x);
if (x <= linScale.domain()[0]) return zeroPoint - logScale(-x);
return linScale(x);
}
scale.invert = function (y) {
if (y > zeroPoint + 0.1) return 0 + logScale.invert(y - zeroPoint);
if (y < zeroPoint - 0.1) return 0 - logScale.invert(zeroPoint - y);
return linScale.invert(y);
}
// Returns what 0 in the domain maps onto in the range
function recalculateZeroPoint() {
// We can't just split proportionally because it's logorathmic
// if we have [-20, 40] -> [10, 100]
// then we want ticks like this:
// 10 20 30 40 50 60 70 80 90 100
// -20 -2 0 2 20 200
//
// i.e. we split the space proportionally to the logarithm of the lenghth
var positiveLength = domain[1] - 0;
var negativeLength = 0 - domain[0];
var positiveProportion = Math.log(positiveLength)/(Math.log(negativeLength) + Math.log(positiveLength));
var negativeProportion = 1 - positiveProportion;
// Take the distance of the negative half into the domain
zeroPoint = (negativeProportion * length(range)) + range[0];
}
function length(interval) {
return interval[1] - interval[0];
}
scale.logScale = function () {
return logScale;
}
scale.linScale = function () {
return linScale;
}
// Domain will be e.g. [-5, 10]
// In this case we want the domain of the positiveScale to be 1 to 10
scale.domain = function(x) {
if (!arguments.length) return domain;
var low = x[0];
if (low > -linearPartDistance) low = -linearPartDistance;
var high = x[1];
if (high < +linearPartDistance) high = +linearPartDistance;
domain = x; recalculateZeroPoint();
positiveLength = high - linearPartDistance;
negativeLength = linearPartDistance - low;
logScale.domain([linearPartDistance, Math.max(positiveLength, negativeLength)]);
return scale;
};
function calculateLogScaleRange(overallRange) {
var positiveLength = overallRange[1] - zeroPoint;
var negativeLength = zeroPoint - overallRange[0];
return [0, Math.max(negativeLength, positiveLength) - linearPartDistance];
}
function filterTicks(ticks, takeEvery) {
return _.filter(ticks, function (x, index) { return (index % takeEvery) == takeEvery - 1; });
}
// Domain will be e.g. [50, 200]
// e.g. neg log scale: [1, 5] => [50, 100]
// pos log scale: [1, 10] => [100, 200]
scale.rangeRound = function(x) {
if (!arguments.length) return range;
range = x;
recalculateZeroPoint();
logScale.rangeRound(calculateLogScaleRange(x));
linScale.rangeRound([zeroPoint - logScale(linearPartDistance), zeroPoint + logScale(linearPartDistance)]);
return scale;
};
scale.range = function(x) {
if (!arguments.length) return range;
range = x;
recalculateZeroPoint();
logScale.range(calculateLogScaleRange(x));
linScale.range([zeroPoint - logScale(linearPartDistance), zeroPoint + logScale(linearPartDistance)]);
return scale;
};
scale.ticks = function(m) {
var logScaleTicks = _.filter(logScale.ticks(m), function (x) { return x > linearPartDistance; });
var negativeTicks = _.map(
_.filter(logScaleTicks, function(x) { return x <= domain[0] * -1; }),
function(x) { return -x; }
);
var positiveTicks = _.filter(logScaleTicks, function(x) { return x <= domain[1]; });
return filterTicks(negativeTicks, 9).reverse()
.concat([0])
.concat(filterTicks(positiveTicks, 9));
};
scale.copyProperties = function (otherLogScale, r, d, z, linSc) {
logScale = otherLogScale;
range = r;
domain = d;
zeroPoint = z;
linScale = linSc
return scale;
};
scale.copy = function(o) {
return GenericLog()
.copyProperties(logScale, range, domain, zeroPoint, linScale);
}
return scale;
}