-
Notifications
You must be signed in to change notification settings - Fork 19
WDFN-67 - Create a symlog-like graph, using a "compound scale". #89
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ | |
}, | ||
"globals": { | ||
"document": true, | ||
"Math": true, | ||
"XMLHttpRequest": true, | ||
"window": true | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ dist/ | |
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
./lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
export default function compound() { | ||
var scales = [].slice.call(arguments); | ||
if (scales.length === 0) { | ||
return null; | ||
} | ||
|
||
function scale(x) { | ||
for (var i = 0; i < scales.length; i++) { | ||
var domain = scales[i].domain(); | ||
if (Math.min.apply(null, domain) <= x && x <= Math.max.apply(null, domain)) { | ||
return scales[i](x); | ||
} | ||
} | ||
// Fallback to last scale | ||
return scales[scales.length - 1](x); | ||
} | ||
|
||
scale.domain = function() { | ||
if (arguments.length) { | ||
throw 'Setting a domain is not supported on compound scales'; | ||
} | ||
var values = [].concat.apply([], scales.map(function(s) { return s.domain(); })); | ||
var domain = [Math.min.apply(null, values), Math.max.apply(null, values)]; | ||
if (values[0] > values[1]) domain = domain.slice().reverse(); | ||
return domain; | ||
}; | ||
|
||
scale.range = function() { | ||
if (arguments.length) { | ||
throw 'Setting a range is not supported on compound scales'; | ||
} | ||
var values = [].concat.apply([], scales.map(function(s) { return s.range(); })); | ||
var range = [Math.min.apply(null, values), Math.max.apply(null, values)]; | ||
if (values[0] > values[1]) range = range.slice().reverse(); | ||
return range; | ||
}; | ||
|
||
scale.copy = function() { | ||
return compound.apply(null, scales.map(function(s) { return s.copy(); })); | ||
}; | ||
|
||
scale.scales = function(_) { | ||
return arguments.length ? (scales = _, scale) : scales; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not understanding this syntax. Can you describe what this does? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the function is called without arguments, it returns the scales array. Otherwise, it sets the array to the passed in parameters and then returns the scale object, enabling function chaining. That clause is using the comma operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Really need to read up on ES6 syntax. |
||
}; | ||
|
||
return scale; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
const { default: compound } = require('./compound'); | ||
const { scaleLinear: linear, scaleLog: log } = require('d3-scale'); | ||
|
||
function intersection(r1, r2) { | ||
var reverse = r1[0] > r1[1]; | ||
if (reverse) r1 = r1.slice().reverse(); | ||
|
||
var min = r1[0] < r2[0] ? r1 : r2; | ||
var max = min === r1 ? r2 : r1; | ||
if (min[1] < max[0]) return null; | ||
|
||
var section = [max[0], min[1] < max[1] ? min[1] : max[1]]; | ||
if (section[0] === section[1]) return null; | ||
|
||
if (reverse) section = section.slice().reverse(); | ||
return section; | ||
} | ||
|
||
function rescale(range, domain) { | ||
var logScale = log(), | ||
d, | ||
parts = []; | ||
|
||
// Negative log scale | ||
if (d = intersection(domain, [Number.NEGATIVE_INFINITY, -1])) { | ||
parts.push({ | ||
domain: d, | ||
type: log, | ||
extent: logScale(Math.abs(d[1] - d[0]) + 1) - logScale(1) | ||
}); | ||
} | ||
|
||
// Linear scale | ||
if (d = intersection(domain, [-1, 1])) { | ||
parts.push({ | ||
domain: d, | ||
type: linear, | ||
extent: Math.abs(d[1] - d[0]) * (logScale(2) - logScale(1)) | ||
}); | ||
} | ||
|
||
// Positive log scale | ||
if (d = intersection(domain, [1, Number.POSITIVE_INFINITY])) { | ||
parts.push({ | ||
domain: d, | ||
type: log, | ||
extent: logScale(Math.abs(d[1] - d[0]) + 1) - logScale(1) | ||
}); | ||
} | ||
|
||
// Create the scales | ||
var scales = []; | ||
var rangeSize = range[1] - range[0]; | ||
var rangeExtent = parts.reduce(function (acc, part) { return part.extent + acc; }, 0); | ||
var rangeStart = range[0]; | ||
for (var i = 0; i < parts.length; i++) { | ||
var part = parts[i]; | ||
if (part.extent > 0) { | ||
var ratio = part.extent / rangeExtent; | ||
var next = (i === parts.length - 1) ? range[1] : rangeStart + ratio * rangeSize; | ||
scales.push(part.type().domain(part.domain).range([rangeStart, next])); | ||
rangeStart = next; | ||
} | ||
} | ||
|
||
return scales; | ||
} | ||
|
||
export default function symlog() { | ||
var scale = compound(linear()), | ||
compoundRange = scale.range, | ||
compoundDomain = scale.domain; | ||
|
||
scale.domain = function(_) { | ||
return arguments.length ? scale.scales(rescale(scale.range(), _)) : compoundDomain(); | ||
}; | ||
|
||
scale.range = function(_) { | ||
return arguments.length ? scale.scales(rescale(_, scale.domain())) : compoundRange(); | ||
}; | ||
|
||
return scale; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just curious about the
var
vslet
here?Also, it seems that this stylistically a bit different from the rest of the javascript... 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a copy-paste of the code I submitted to d3-scale, so I wrote it in the D3 style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Makes sense.