Change min max color range on d3.scaleLinear().domain() - dc.js

How change min and max value in color range for dc.scatterPlot,
var heatColorMapping = d3.scaleLinear()
.domain([1, hoursDaysGroup.top(1)[0].value/2, hoursDaysGroup.top(1)[0].value])
.range(["rgb(17, 95, 154)", "rgb(118, 198, 143)", "rgb(208, 244, 0)"]);
when select new filter from dc.rowChart in dashboard -
https://jsfiddle.net/gubzh/th4m5xwr/20/
Thank you in advance.

Related

Heatmap color range based on max & min value

I am trying to leverage this example for my visualization
https://bl.ocks.org/Bl3f/cdb5ad854b376765fa99
It pretty much works except this part
var colorScale = d3.scale.threshold()
.domain([0.85, 1])
.range(["#2980B9", "#E67E22", "#27AE60", "#27AE60"]);
I basically want a standard vibgyor visualization based on min & max value of the values in csv data?
Any suggestions, how I can modify the above example.
Thanks
If you want a Violet–Indigo–Blue–Green–Yellow–Orange–Red scale, you can use d3.interpolateRainbow with a sequential scale.
Then, set your domain using the min and max in your values, like any other continuous scale.
Here is a basic example. Suppose this data:
const data = [12, 43, 76, 54, 87, 91, 17, 42, 36];
We set the scale like this:
const scale = d3.scaleSequential()
.domain(d3.extent(data))
.interpolator(d3.interpolateRainbow);
Running demo:
const data = [12, 43, 76, 54, 87, 91, 17, 42, 36];
const scale = d3.scaleSequential()
.domain(d3.extent(data))
.interpolator(d3.interpolateRainbow);
d3.select("body").selectAll(null)
.data(data)
.enter()
.append("div")
.style("background-color", d => scale(d))
div {
display: inline-block;
width: 20px;
height: 20px;
margin-left: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Have in mind that this scale won't work for the specific example you linked, since that uses the old D3 v3.

How to match up scaleBand with scaleLinear in D3.js

I have two series of scale, one is linear and the other is band, how can I make them to match up if there is some caps in the data.
Take a look at the example if necessary.
Mouse over and you see the boxes are not matching with the breaks of line.
If you want your scaleBand to be scaled (widened) where data is missing, I don't think that the scaleBand is the proper method for this, but it is unclear if that is something you want. Band scales are intended to provide equal spacing for each data value and that all values are present - it is an ordinal scale.
Assuming you only want the band scale to be aligned with your data where it is present:
If you log the domains of each of your x scales (scaleBand and scaleLinear) we find that the scaleBand has a domain of:
[ "1", "2", "8", "9", "13", "14", "20", "22" ] // 8 elements
And the scaleLinear has a domain of:
[ 1, 22 ] // a span of 22 'elements'
The scaleBand will need an equivalent domain to the scaleLinear. You can do this statically ( which I show mostly to demonstrate how d3.range will work):
let xBand = d3.scaleBand()
.domain(d3.range(1,23))
.rangeRound([0, width]);
This actually produces a domain that has 22 elements from 1 through 22.
or dynamically:
let xBand = d3.scaleBand()
.domain(d3.range(d3.min(testData1, d => d[0],
d3.max(testData1, d => d[0]+1)))
You could do this other ways, but the d3.range() function is nice and easy.
However, there is still one issue that remains, this is aligning the ticks between the two scales. For the linear scale, the tick for the first value (1) is on the y axis, but the band gap scale starts (and is not centered) on the y axis and fills the gap between 1 and 2. In other words, the center point of the band does not align vertically with the vertices of the line graph.
This can be addressed by adding 0.5 to both the lower and upper bounds of the linear scale's domain:
let xDomain = [
d3.min(testData1, d => d[0]-0.5),
d3.max(testData1, d => d[0]+0.5)
];
I've udpated your codepen with the relevant changes: codepen.
And in the event that that disappears, here is a snippet (the mouse over does not work for me for some reason in the snippet, it does in the codepen )
let width = 1000;
let height = 300;
let svg = d3.select(".wrapper-area-simple").append("svg")
.attr("width", width + 80)
.attr("height", height + 80)
.append('svg:g')
.attr('transform', 'translate(40, 30)');
let testData1 = [
[ 1, 10],
[ 2, 30],
[ 8, 34],
[ 9, 26],
[13, 37],
[14, 12],
[20, 23],
[22, 16],
];
let xDomain = [
d3.min(testData1, d => d[0]-0.5),
d3.max(testData1, d => d[0]+0.5)
];
let x = d3.scaleLinear()
.rangeRound([0, width])
.domain(xDomain);
let y = d3.scaleLinear()
.range([height, 0])
.domain(d3.extent(testData1, d => d[1]));
let line = d3.line()
.x(d => x(d[0]))
.y(d => y(d[1]));
svg.append('svg:g')
.datum(testData1)
.append('svg:path')
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', '#000');
let xAxis = d3.axisBottom(x)
.ticks(testData1.length);
svg.append('svg:g')
.call(xAxis)
.attr('transform', `translate(0, 300)`);
let xBand = d3.scaleBand()
.domain(d3.range(d3.min(testData1, d => d[0]),
d3.max(testData1, d => d[0]+1)
))
.rangeRound([0, width]);
svg.append('svg:g')
.selectAll('rect')
.data(testData1)
.enter()
.append('svg:rect')
.attr('x', d => xBand(d[0]))
.attr('width', xBand.bandwidth())
.attr('height', height)
.attr('fill', '#000')
.on('mouseover', function() {
d3.select(this).classed('over', true);
})
.on('mouseout', function() {
d3.select(this).classed('over', false);
});
svg {
border: 1px solid red;
}
rect {
opacity: .1;
}
rect.over {
opacity: .2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"> </script>
<div class="wrapper-area-simple"></div>
Well, bad news for you: they will never match up (in your case). Let's see why.
This is your data:
let testData1 = [
[1, 10],
[2, 30],
[8, 34],
[9, 26],
[13, 37],
[14, 12],
[20, 23],
[22, 16],
];
As you can see, regarding the x coordinate, the line jumps from 1 to 2, but then from 2 to 8, from 8 to 9, and then from 9 to 13... That is, the x range intervals are not regular, evenly spaced. So far, so good.
However, when you pass the same data to the band scale, this is what it does: it divides the range ([0, width], which is basically the width) by testData1.length, that is, it divides the range by 8, and creates 8 equal intervals. Those are your bands, and that's the expected behaviour of the band scale. From the documentation:
Discrete output values are automatically computed by the scale by dividing the continuous range into uniform bands. (emphasis mine)
One solution here is simply using another linear scale:
let xBand = d3.scaleLinear()
.domain(xDomain)
.rangeRound([0, width]);
And this math to the width of the rectangles:
.attr('width', (d,i) => testData1[i+1] ? xBand(testData1[i+1][0]) - xBand(d[0]) : 0)
Here is your updated Codepen: http://codepen.io/anon/pen/MJdGyY?editors=0010

d3.js. Stream chart shifts with different data sets. Why?

Code available here http://jsfiddle.net/zeleniy/ea1uL7w9/
data = [47, 13, 61, 46, 26, 32, 6, 85, 1, 14, 86, 77, 13, 66, 0, 20, 11, 87, 5, 15];
data = [52, 33, 53, 45, 59, 45, 42, 50, 53, 50, 37, 45, 52, 50, 46, 48, 52, 56, 58, 59];
width = 300;
height = 100;
xScale = d3.scale.linear()
.domain([0, data.length])
.range([0, width]);
yScale = d3.scale.linear()
.domain([d3.min(data), d3.max(data)])
.range([0, height])
area = d3.svg.area()
.interpolate("basis")
.x(function(d, i) { return xScale(i); })
.y0(function(d) { return yScale(-d / 2); })
.y1(function(d) { return yScale(d / 2); });
svg = d3.select("#stream")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.selectAll("path")
.data([this.data])
.enter()
.append("path")
.attr("transform", "translate(0," + (height / 2) + ")")
.style("fill", "red")
.attr("d", area);
With first data set chart drawn in the center of svg element, as i expect. But with second data set stream shifts to the top of svg element. And i can't understand why. So why?
The first array contains values close to 0 and it's opening up your range. This line, then, is a fudge to shift the path into that open window:
.attr("transform", "translate(0," + (height / 2) + ")")
That said, you are setting up your scales in a confusing way (to me at least). I think about my domain as the min/max of my (plotted) dataset, in your case -max(d/2) and max(d/2). Further, I also think about my y-scale going from bottom to top as it would in a normal plot. With these changes, you don't need to artificially move anything:
var dataMax = d3.max(data);
yScale = d3.scale.linear()
.domain([ -dataMax/2, dataMax/2 ]) // real min/max of plotted data
.range([height, 0]) //<- bottom to top, although it still works without this change...
In this example, I left an axis overlayed for illustration.

Drawing linechart in Swift with 1000+ datapoints

So I have Custom view an I'm trying to draw line chart there. First I draw xGrid, yGrid and middleline. Here are my functions to do that:
var width: CGFloat = 400
var height: CGFloat = 200
var xLines: CGFloat = 20
var yLines: CGFloat = 10
let color = NSColor.blackColor()
func drawMiddleLine() {
let middleHeight = height / 2
var context = NSGraphicsContext.currentContext()?.CGContext
CGContextSetStrokeColorWithColor(context, color.CGColor)
CGContextSetLineWidth(context, 2)
CGContextMoveToPoint(context, 0, middleHeight)
CGContextAddLineToPoint(context, width, middleHeight)
CGContextStrokePath(context)
}
func drawYGrid() {
let space = height / yLines
var context = NSGraphicsContext.currentContext()?.CGContext
CGContextSetStrokeColorWithColor(context, color.CGColor)
for index in 0...Int(yLines) {
CGContextMoveToPoint(context, width, (CGFloat(index) * space))
CGContextAddLineToPoint(context, 0, (CGFloat(index) * space))
}
CGContextStrokePath(context)
}
func drawXGrid() {
let space = width / xLines
var context = NSGraphicsContext.currentContext()?.CGContext
CGContextSetStrokeColorWithColor(context, color.CGColor)
for index in 0...Int(xLines) {
CGContextMoveToPoint(context, (CGFloat(index) * space), self.bounds.height)
CGContextAddLineToPoint(context, CGFloat(index) * space, 0)
}
CGContextStrokePath(context)
}
Now I have basic grid which width is two times height. Now I want to scale my y-axis, so I'm going to scale my data (In this example, I only take max positive number):
func getMaxValue(data: Array<CGFloat>) ->CGFloat {
let max = maxElement(data)
return max
}
And now I scale my y-Axis:
func scaleYAxis(data: Array<CGFloat>) ->Array<CGFloat> {
let maxValue = getMaxValue(data)
var factor = height / maxValue / 4
var scaledY = data.map({datum -> CGFloat in
var newValue = datum * factor
return newValue
})
return scaledY
}
But when I'm drawing my line with too many datapoints, my drawing will get messed up, because there is too many datapoints. It's ok when there is about 50 datapoints.
For example, I want to get something like this in results: JavaScript linechart with many many datapoints
Any ideas how can I manage to get that in Swift?
And my drawing method is something like that:
func drawLine(data: Array<CGFloat>) {
var path = CGPathCreateMutable()
var middleHeight = height / 2
CGPathMoveToPoint(path, nil, 0, middleHeight)
var scaledY = sclaleYAxis(data)
for index in 0..<data.count {
var xSpot = data[index]
var ySpot = middleHeight + scaledY[index]
CGPathAddLineToPoint(path, nil, xSpot, ySpot)
}
var layer = CAShapeLayer()
layer.frame = self.bounds
layer.path = path
layer.strokeColor = NSColor.greenColor().CGColor
layer.fillColor = nil
layer.lineWidth = 3
self.layer?.addSublayer(layer)
// I Have lineLayerStore and I delete all lines when it is needed
lineLayerStore.append(layer)
}
I cannot understand what the exact problem is without seeing a screenshot but visibility of the datapoints depends on the number of pixels on the screen.
You cannot display more datapoints than the number of pixels, without letting some datapoints share the same pixel column or downsampling the data.
You have 3 options:
Make X axis scrollable and set scrollview's contentSize width to the
number data points.
Keep X axis width fixed, downsample the data to reduce the number of
datapoints to be displayed, then draw the chart regarding the
downsampled data.
Do nothing, try to draw all the datapoints within the fixed width,
if your calculations are correct some datapoints will overlap and
share the same pixel column in the chart.
Explanation Edit for 3rd option:
For displaying 1000 datapoints on a 200 pixels wide X axis, there will be 5 datapoints per pixel. So;
Datapoints 0, 1, 2, 3 and 4 will be drawn on 1st pixel column.
Datapoints 5, 6, 7, 8 and 9 will be drawn on 2nd pixel column.
Datapoints 10, 11, 12, 13 and 14 will be drawn on 3rd pixel column.
And so on.
For your example, there are 400 pixels and 1000 datapoints, it means 2.5 datapoint per pixel. So;
Datapoints 0, 1, 2 will be drawn on 1st pixel column.
Datapoints 3, 4 will be drawn on 2nd pixel column.
Datapoints 5, 6, 7 will be drawn on 3rd pixel column.
Datapoints 8, 9 will be drawn on 4th pixel column.
And going on like that.

d3.js ticks function giving more elements than needed

I have this simple linear scale:
var x = d3.scale.linear().domain([0, 250]);
x.ticks(6), as expected, returns:
[0, 50, 100, 150, 200, 250]
However, x.ticks(11) returns:
[0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240]
When what I want is:
[0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250]
How do I fix this?
I had a similar issue with ordinal scales, I simply wrote some code to pick evenly spaced intervals in my data. Since I wanted it to always choose the first and last data element on the axis, I calculate the middle part only. Since some things do not divide evenly, rather than having the residual in one or two bins, I spread it out across the bins as I go; until there is no more residual.
There is probably a simpler way to accomplish this but here's what I did:
function getTickValues(data, numValues, accessor)
{
var interval, residual, tickIndices, last, i;
if (numValues <= 0)
{
tickIndices = [];
}
else if (numValues == 1)
{
tickIndices = [ Math.floor(numValues/2) ];
}
else
{
// We have at least 2 ticks to display.
// Calculate the rough interval between ticks.
interval = Math.floor(data.length / (numValues-1));
// If it's not perfect, record it in the residual.
residual = Math.floor(data.length % (numValues-1));
// Always label our first datapoint.
tickIndices = [0];
// Set stop point on the interior ticks.
last = data.length-interval;
// Figure out the interior ticks, gently drift to accommodate
// the residual.
for (i=interval; i<last; i+=interval)
{
if (residual > 0)
{
i += 1;
residual -= 1;
}
tickIndices.push(i);
}
// Always graph the last tick.
tickIndices.push(data.length-1);
}
if (accessor)
{
return tickIndices.map(function(d) { return accessor(d); });
}
return tickIndices.map(function(i) { return data[i]; });
}
You call the function via:
getTickvalues(yourData, numValues, [optionalAccessor]);
Where yourData is your array of data, numvalues is the number of ticks you want. If your array contains a complex datastructure then the optional accessor comes in handy.
Lastly, you then feed this into your axis. Instead of ticks(numTicks) which is only a hint to d3 apparently, you call tickValues() instead.
I learned the hard way that your tickValues have to match your data exactly for ordinal scales. This may or may not be as helpful for linear scales, but I thought I'd share it anyways.
Hope this helps.
Pat
You can fix this by replacing the x.ticks(11) with your desired array.
So if your code looks like this and x is your linear scale:
chart.selectAll("line")
.data(x.ticks(11))
.enter()
.append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2",120)
.style("stroke", "#CCC");
You can replace x.ticks(11) with your array:
var desiredArray = [0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250]
chart.selectAll("line")
.data(desiredArray)
.enter()
.append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2",120)
.style("stroke", "#CCC");
The linear scale will automatically place your desired axes based on your input. The reason why the ticks() isn't giving you your desired separation is because d3 just treats ticks() as a suggestion.
axis.tickvalues((function(last, values) {
var myArray = [0];
for(var i = 1; i < values; i++) {
myArray.push(last*i/(values-1))
}
return myArray;
})(250, 11));
This should give you an evenly spaced out array for specifying the number of tick values you want in a particular range.

Resources