Intially I have the decreasing line plot but when I do cross filtering it won't be decreasing any more as the x-axis labels become fixed. I want this X-axis labels to be dynamic and to be arranged on the basis of the y-axis values. I ain't able to find the syntax or what to be used for this case.
I initialize my chart like
lineChart2
.height(600)
.width(lineChart2.width()*0.95) //give it a width
// .margins({top: 10, right: 10, bottom: 20, left: 100})//give it margin left of 100 so that the y axis ticks dont cut off
.dimension(lineTypeDimension)
.group(lineTypeGroup)
.ordering(function(kv) {
console.log("val:",kv.value);
return -kv.value; })
.x(d3.scaleOrdinal( ))
.xUnits(dc.units.ordinal)
.interpolate('linear')
.elasticY(true)
.renderArea(true)
.renderlet(function(chart){chart.selectAll("circle.dot").style("fill-opacity", 1).on('mousemove', null).on('mouseout', null);})
// .renderTitle(true)
.renderLabel(true)
.xAxis().ticks(3).tickFormat(function(d) {
return d;
});
Here is the working demo:
https://blockbuilder.org/ninjakx/302eaacb0a333e67c46c55dd60d27811
You can get the line chart to recalculate the X domain on each redraw by setting
lineChart2
.elasticX(true)
The feature was originally intended for linear scales in order to calculate the min and max each redraw. Ordinal scales work differently, with a sequence of domain and range values, but setting elasticX will will get the chart to recalculate the scale.
Related
I have to create a custom stock chart with variable width x axis for each date.
I am using d3.scaleOrdinal for creating the x-axis
const xScale = d3.scaleOrdinal()
.domain(["16-09-2022", "17-09-2022", "18-09-2022", "19-09-2022", "20-09-2022"])
.range([0, 260, 500, 750, 900,1010]);
While zooming using geometric approach, the axis got scaled as a whole(i.e ticks, ticklabel, axis line) which I want to work like scaleLinear.
svg.select("#xaxis")
.attr("transform", `translate(${transform.x},${margin.top})scale(${transform.k}) `)
Can anybody suggest how to achieve the desired result with d3 v7
Thanks in advance
You need to use a continuous scale, like scaleLinear, otherwise it won't work because the ordinal scale cannot interpolate between values.
Think of it like having an array with indexes 0, 1, 2. There is no index 0.5 or 1.5, because ordinals are discrete whole values.
I have a dc.js heatmap working:
But I want to add grid lines to it, like so:
You can see that the lines to not match up with the bottom edges of the rects. Inserting the lines themselves is easy, you just start at zero and add 11 lines based on the height of the rects, which in this case will always be 11 / chart.effectiveHeight().
The reason they do not match up, seems to be that the top rect row does not always start at 0, instead, there seems to be a random(?) y position that the chart starts at, this will change with the height of the chart container, eg this y position starts at 5:
If it was consistent, then I could just start appending lines from that number instead of 0, but it is not. I have tried a couple of hacky work arounds, however I am unsure as to how to get the y position of all the rects after they are available in the DOM.
Interestingly the demo heatmap does not have this issue:
Here is the code for the heatmap:
const heat_map = dc.heatMap('#heatmap');
heat_map
.width(0)
.height(0)
.margins(margins)
.dimension(hm_dim)
.group(hm_group)
.keyAccessor(function(d) { return +d.key[0]; })
.valueAccessor(function(d) { return +d.key[1]; })
.colorAccessor(function(d) { return +d.value; })
.colors(color_scale)
.calculateColorDomain()
.yBorderRadius(0)
.xBorderRadius(0)
heat_map.render();
Is there a way to force the rects to begin at 0? Or get the random y position for the top rows? I did have a look at the source code but got a bit lost. Also I thought about creating a false group that would include each rect in the grid, and the grid lines could then be rect borders, but I thought that was a bit heavy handed.
Outlining the cells using CSS
It's easy to outline the cells using CSS:
rect.heat-box {
stroke-width: 1;
stroke: black;
}
Example fiddle.
However, as you point out, this only works if all the cells have values; crossfilter will not create the empty ones and I agree it would be absurd fill them in using a fake group just for some lines.
So, to answer your original question...
Why is there a gap at the top of the chart?
The heatmap calculates an integer size for the cells, and there may be space left over (since the space doesn't divide perfectly).
It's kind of nasty but the heatmap example avoids having extra space by calculating the width and height for the chart using the count of cells in each dimension:
chart
.width(45 * 20 + 80)
.height(45 * 5 + 40)
The default margins are {top: 10, right: 50, bottom: 30, left: 30} so this allocates 45x45 pixels for each cell and adds on the margins to get the right chart size.
Since the heatmap in this example draws 20 columns by 5 rows, it will calculate the cell width and height as 45.
Alternative Answer for Responsive/Resizable Charts
I am revisiting this question after rewriting my heatmap chart to be responsive - using the "ResizeObserver" method outlined in the dc.js resizing examples and Gordon's answer to this question
While specifying the chart width and height for the heatmap in Gordon's answer still works, it does not combine well with the resizing method because resized charts will have their .width and .height set to 'null'. Which means that this rounding issue will reoccur and the heat boxes will be again be offset by a random integer x or y value of anywhere between 0 and 5 (unless you want to write a custom resizing function for heatmaps).
The alternative answer is relatively simple and can be determined by selecting just one heat-box element in the heatmap.
The vertical offset value for the heat boxes is the remainder value when the heat-box y attribute is divided by the heat-box height attribute.
const heatbox_y = heat_map.select('.heat-box').attr('y);
const heatbox_height = heat_map.select('.heat-box').attr('height')
const vertical_offset = heatbox_y % heatbox_height
The modulus % will return the remainder.
The horizontal offset can be determined in the same way.
Thus you can append lines to the chart at regular intervals determined by the heatbox_height + the vertical_offset values.
This will work if you pick any heat-box in the chart, and so it is suitable for instances like this where you cannot guarantee that there will be a heat-box at each x or y level. And it means that you are free to set your chart height and width to 'null' if needed.
I want to set the pixel radius of the bubbles on my bubbleChart. All bubbles will be 3px when bubbles represent companies, but 6px when they represent portfolios, and so on for a total of four categories. So the user can aggregate the data behind the bubbles at different levels, and the size of the bubbles should represent that.
Problem is I am struggling to set the radius in pixels.
bubbleChart.width(738)
.height(315)
.margins({left:40,right:30,top:15,bottom:30})
.dimension(idDimBubble)
.group(idGrpBubble)
.clipPadding(10)
.elasticY(true)
.elasticX(true)
//.mouseZoomable(true)
// .elasticRadius(true)
.renderLabel(false)
.keyAccessor(function (p) { return p.value[selectArrays[0][1]] / p.value.name.length; })
.valueAccessor(function (p) { return p.value[selectArrays[0][0]] / p.value.name.length; })
.radiusValueAccessor(function (p) { return (filters[0].indexOf(window.gran)+1); })// this outputs a digit of 1,2, 3 or 4. I want that to correspond to a radius of 3,6,9,12.
//.maxBubbleRelativeSize(0.05)
.x(d3.scale.linear().domain([0, 100]))
.y(d3.scale.linear().domain([0, 100]))
.r(d3.scale.linear().domain([1, 4]).range([1,4]))
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true);
I would think the code above would output a pixel radius of between 1 and 4px. Instead, I have to maxBubbleRelativeSize to suppress the size because all four bubbles are too big. Even the smallest bubble is more like a radius of 10 not 1px.
Does anyone know how I can set the actual pixel size directly? Thanks
I have a zoomable area plot done in D3, which works well. Now I am trying to add a rectangle to the specified location along x-axis in the middle of the plot. However, I can't seem to figure out how to do that. "rect" element is specified using absolute (x,y) of the plot and so when using zooms it stays in the same position.
So I was wondering if there is a way to tie "rect" to the axis when plotting, so that it benefits from all the zoom and translate behaviour or do I need to manually edit the x,y,width and length of the rectangle according to translation as well as figuring out where the corresponding x and y coordinates are on the graph? I am trying to use "rect" because it seems the most flexible element to use.
Thanks
Alex
I'm not sure how you are doing the zooming, but I am guessing you are changing the parameters of the scales you use with your axis? You should be able to use the same scales to place your rectangle.
If you are starting with plot coordinates then maybe using the invert function on the scale will help (available at least for quantitive scales), e.g. https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear_invert
You should be able to take initial plot coordinates and invert them to determine data coordinates that can then move with changes in the scale.
If the scale is linear you can probably invert the length and width too, but you will have to compute offsets if your domain does not include 0. Easiest is to compute the rectangle's end points, something like:
var dataX0 = xScale.invert(rect.x);
var dataX1 = xScale.invert(rect.x + rect.width);
var dataWidth = dataX1 - dataX0;
If you have the data in axes coordinates already you should be able to do something like:
var rectData = [{x: 'April 1, 1999', y: 10000, width: 100, height:100}];
svg.selectAll('rect.boxy')
.data(rectData)
.enter().append('rect').classed('boxy', true)
.style('fill','black');
svg.selectAll('rect.boxy')
.attr('x', function(d) { return x(new Date(d.x));} )
.attr('y', function(d) { return y(d.y);})
.attr('width', function(d) { return d.width;} )
.attr('height', function(d) { return d.height;} );
Based on the example you shared where x and y (as functions) are the scales the axes are based on.
I need to create a d3 bar chart that can have negative values. Ideally the axis zero position should be calculated based on the extent of the data, but I'd settle for a solution that assumes symmetric positive and negative extent, i.e. that it would be always in the middle of the chart.
Here's an example of what I'd like to achieve.
OK, let's say you have an array of numbers as your dataset, and this includes some positive and negative values:
var data = [-15, -20, -22, -18, 2, 6, -26, -18];
You'll want two scales to construct a bar chart. You need one quantitative scale (typically a linear scale) to compute the bar positions along the x-axis, and a second ordinal scale to compute the bar positions along the y-axis.
For the quantitative scale, you typically need to compute the domain of your data, which is based on the minimum and maximum value. An easy way to do that is via d3.extent:
var x = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width]);
You might also want to nice the scale to round the extent slightly. As another example, sometimes you want the zero-value to be centered in the middle of the canvas, in which case you'll want to take the greater of the minimum and maximum value:
var x0 = Math.max(-d3.min(data), d3.max(data));
var x = d3.scale.linear()
.domain([-x0, x0])
.range([0, width])
.nice();
Alternatively, you can hard-code whatever domain you want.
var x = d3.scale.linear()
.domain([-30, 30])
.range([0, width]);
For the y-axis, you'll want to use rangeRoundBands to divide the vertical space into bands for each bar. This also lets you specify the amount of padding between bars. Often an ordinal scale is used with some identifying data—such as a name or a unique id. However, you can also use ordinal scales in conjunction with the data's index:
var y = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, height], .2);
Now that you've got your two scales, you can create the rect elements to display the bars. The one tricky part is that in SVG, rects are positioned (the x and y attributes) based on their top-left corner. So we need to use the x- and y-scales to compute the position of the top-left corner, and that depends on whether the associated value is positive or negative: if the value is positive, then the data value determines the right edge of the bar, while if it's negative, it determines the left edge of the bar. Hence the conditionals here:
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d, i) { return x(Math.min(0, d)); })
.attr("y", function(d, i) { return y(i); })
.attr("width", function(d, i) { return Math.abs(x(d) - x(0)); })
.attr("height", y.rangeBand());
Lastly, you can add an axis to display tick marks on top. You might also compute a fill style (or even a gradient) to alter the differentiate the appearance of positive and negative values. Putting it all together:
Bar Chart with Negative Values