Is it possible to set a max-width and max-height for cells in Handsontable?
I've tried to set this via CSS on the <th> and <td> elements but this doesn't work.
I saw in the docs that you can set the columns to particular widths, but not maximums.
So, the solution is to use your HOT instance's manualColumnWidths array. This array will set the widths for all columns automatically. It's nested somewhere in the code so that anytime there's a change to the DOM it updates which is why using CSS or jQuery on the widths of the elements themselves wasn't working. It's a good structure, we just need this to be more explicitly written somewhere.
Note that you can repeat this for rows by just using manualRowHeights instead, as well as the rest of the row methods.
Now, here is how you would set maximums and minimums for column resizing events. Set the afterColumnResize option to this following function:
function setAfterColumnResize() {
return function(col, size) {
var headers = $(".colHeader"); // first is empty if there are rowHeaders
var maxW = [10, 200, 400]; // note that the first is the row header
var minW = [1, 100, 100]; // note that the first is the row header
var actualCol = col + 1; // this is to account for the row header
var maxColW = maxW[actualCol];
var minColW = minW[actualCol];
var actualW = $(headers[actualCol]).parent().parent().width();
if (hot && maxColW && actualW > maxColW) {
hot.manualColumnWidths[col] = maxColW;
hot.render()
}
if (hot && minColW && actualW < minColW) {
hot.manualColumnWidths[col] = minColW;
hot.render()
}
}
}
Now let me explain it. When this event gets triggered, you first find the header using the col index it gives you. Correct it if you have row headers set to true (which is what I do here and in the fiddle). Then you set the arrays of max/min widths (of course you can do this more elegantly in your code).
Then grab the actual current width of the column in question by looking at the parent's parent of the span (this is a little messy but it's the simplest way I found of doing). The conditional after should be self explanatory.
And lastly, the important bits. You can see that by accessing hot.manualColumnWidths you can set the width that you want. I set it to the max if my current width exceeds it. Then just re-render and you're done!
There are a few issues like the fact that these events get called only after resizing so if you originally set the table to render with a width larger than your max, it won't do anything. If that's the case, just call this function after rendering the table the first time (call it once per column).
This would also be the case if you didn't want to use the column resizing event; in this case, it would just be a normal function that you call after rendering.
Hope this helps! Here is a demo fiddle showing it in action :D
Related
I have an heatmap that show some data and a sparkline for each line of the heatmap.
If the user click on a row label, then the data are ordered in decreasing order, so each rect is placed in the right position.
Viceversa, if the user click on a column label.
Each react is placed in the right way but I'm not able to place the sparkline.
Here the code.
When the user click on a row label, also the path inside the svg containing the sparkline should be updated.
And then, when the user click on a column label, the svg containing the sparkline should be placed in the correct line.
To place the svg in the right place, I try to use the x and y attributes of svg. They are updated but the svg doesn't change its position. Why?
Here is a piece of code related to that:
var t = svg.transition().duration(1000);
var values = [];
var sorted;
sorted = d3.range(numRegions).sort(function(a, b) {
if(sortOrder) {
return values[b] - values[a];
}
else {
return values[a] - values[b];
}
});
t.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sorted.indexOf(k) * cellSize;
});
Also, I don't know how to change the path of every sparkline svg. I could take the data and order them manually, but this is only good for the row on which the user has clicked and not for all the others.
How can I do?
The vertical and horizontal re-positioning/redrawing of those sparklines require different approaches:
Vertical adjustment
For this solution I'm using selection.sort, which:
Returns a new selection that contains a copy of each group in this selection sorted according to the compare function. After sorting, re-inserts elements to match the resulting order.
So, first, we set our selection:
var sortedSVG = d3.selectAll(".data-svg")
Then, since selection.sort deals with data, we bind the datum, which is the index of the SVG regarding your sorted array:
.datum(function(d){
return sorted.indexOf(+this.dataset.r)
})
Finally, we compare them in ascending order:
.sort(function(a,b){
return d3.ascending(a,b)
});
Have in mind that the change is immediate, not a slow and nice transition. This is because the elements are re-positioned in the DOM, and the new structure is painted immediately. For having a slow transition, you'll have to deal with HTML and CSS inside the container div (which may be worth a new specific question).
Horizontal adjustment
The issue here is getting all the relevant data from the selection:
var sel = d3.selectAll('rect[data-r=\'' + k + '\']')
.each(function() {
arr.push({value:+d3.select(this).attr('data-value'),
pos: +d3.select(this).attr('data-c')});
});
And sorting it according to data-c. After that, we map the result to a simple array:
var result = arr.sort(function(a,b){
return sorted.indexOf(a.pos) - sorted.indexOf(b.pos)
}).map(function(d){
return d.value
});
Conclusion
Here is the updated Plunker: http://next.plnkr.co/edit/85fIXWxmX0l42cHx or http://plnkr.co/edit/85fIXWxmX0l42cHx
PS: You'll need to re-position the circles as well.
I'm trying to implement a live data visualization (i.e. with new data arriving periodically) using dc.js. The problem I'm having is the following - when new data is added to the plot, already existing points often start to "dance around", even though they were not changed. Can this be avoided?
The following fiddle illustrates this.
My guess is that crossfilter sorts data internally, which results in points moving on the chart for data items that changed their position (index) in the internal storage. Data is added in the following way:
var data = [];
var ndx = crossfilter(data)
setInterval(function() {
var value = ndx.size() + 1;
if (value > 50) {
return;
}
var newElement = {
x: myRandom(),
y: myRandom()
};
ndx.add([newElement]);
dc.redrawAll();
}, 1000);
Any ideas?
I stand by my comments above. dc.js should be fixed by binding the data using a key function, and probably the best way to deal with the problem is just to disable transitions on the scatterplot using .transitionDuration(0)
However, I was curious if it was possible to work around the current problems by keeping the group in a set order using a fake group. And it is indeed, at least for this example where there is no aggregation and we just want to display the original data points.
First, we add a third field, index, to the data. This has to order the data in the same order in which it comes in. As noted in the discussion above, the scatter plot is currently binding data by its index, so we need to keep the points in a set order; nothing should be inserted.
var newElement = {
index: value,
x: myRandom(),
y: myRandom()
};
Next, we have to preserve this index through the binning and aggregation. We could keep it either in the key or in the value, but keeping it in the key seems more fitting:
xyiDimension = ndx.dimension(function(d) {
return [+d.x, +d.y, d.index];
}),
xyiGroup = xyiDimension.group();
The original reduction didn't make sense to me, so I dropped it. We'll just use the default behavior, which counts the number of rows which fall into each bin. The counts should be 1 if included, or 0 if filtered out. Including the index in the key also ensures uniqueness, which the original keys were not guaranteed to have.
Now we can create a fake group that keeps everything sorted by index:
var xyiGroupSorted = {
all: function() {
var ret = xyiGroup.all().slice().sort((a,b) => a.key[2] - b.key[2]);
return ret;
}
}
This will fetch the original data whenever it's requested by the chart, create a copy of the array (because the original is owned by crossfilter), and sort it to return it to the correct order.
And voila, we have a scatter plot that behaves the way it should, even though the data has gone through crossfilter.
Fork of your fiddle: https://jsfiddle.net/gordonwoodhull/mj81m42v/13/
[After all this, maybe we shouldn't have given the data to crossfilter in the first place! We could have just created a fake group which exposes the original data. But maybe there's some use to this technique. At least it proves that there's almost always a way to work around any problems in dc.js & crossfilter.]
I am new to handsontable, and I can not achieve a goal as simple as to have the columns width as long as the content of the cells. Even if I have space enough in handsontable parent to display the full content of the table, some columns overlap the content of some cells.
I do not want to stretch the table to its parent. Just to show the full table contents (as I have space enough).
Update
The answer of fap is right.
I have realized the problem does not come from the basic definition of the table but for the definition of a renderer do on cells.
cells: function (row, col, prop) {
var cellProperties = {};
if (row === 0) {
cellProperties.renderer = firstRowRenderer;
}
return cellProperties;
}
function firstRowRenderer(instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.fontWeight = 'bold';
td.style.color = 'green';
td.style.background = '#CEC';
}
It is after the renderer is applied when the content does not fit into handsontable cells and it does not resize. This is the real problem I am facing.
Just don't specify any width for the table and/or columns. Handsontable will size the columns depending of the longest value there is in their respective cells.
See this simple example.
Note that if you edit the values, the column resize dynamically.
But what if you have a value that expand the column width so much that your table is wider than your screen ? Well, if you don't really want that (and I assume that you don't otherwise what's the point of delimiting your columns and/or your table in the first place ?), you can use the option preventOverflow :
preventOverflow: 'horizontal',
As you can see in this example, it will automatically create a navigate bar that prevent your table to go off screen but still size the columns in order to see all your data.
How to set the alignment and width of all cells the easy way?
Thanks.
Michael
Edit: Here my modified question - How to set the alignment and width of all cells of a large Handsontable? Is it possible to access all cells at once or to iterate over the cells?
For the alignment, just add the the className parameter to the json instance
var hot = new Handsontable(container, {
className: "htCenter", //htCenter or htRight
});
If you want to set the same width and same alignment for every cell in your table, then you simply want to add the following to your options object:
{
"colWidths": 200, // where 200 is an example width in pixels
"className": "htRight" // this is an example; you can choose from
"htRight", "htLeft", "htMiddle", "htBottom", and "htTop"
}
If using a type other than text, the text-align property is given high priority. Therefore, to override this using CSS, you could add the following rule; this is an example on whatever className you used and assuming you want to stick it to the right.
.htCenter {
text-align:right!important;
}
And at any point in your code, you can update these settings using updateSettings().
I'm using SlickGrid and struggling to find an elegant solution to the following:
All columns must have a specific initial width when first rendered but be resizable afterwards
The final column should auto fill the remaining column space when the window is resized
I've seen:
Make one column fill remaining space in SlickGrid without messing up explicit width columns
resizing of grid when resizing browser window
How do I autosize the column in SlickGrid?
But these don't seem to quite do what I need.
If I use the forceFitColumns option, then all columns will autosize (unless I put a maxsize on them).
Using resizeCanvas on window.resize works well - but it still only works if forceFitColumns is true.
If I set minWidth=maxWidth - then I can't resize the column.
Any suggestions?
I'm not sure it would correct all your problem but in my case I do use the forceFitColumns and then depending how I want my column to react in size I will use a combination of minWidth and width, and in some cases the ones that will never exceed a certain width, I would then use a maxWidth as well. Now the problem you have is when setting the minWidth to be the same with as maxWidth this of course will make it unresizable, well think about it you set a minimum and a maximum, SlickGrid is respecting it by now being able to size it afterwards. I also have my grid which takes 95% width of my screen so I have a little padding on the side and with it I use a auto-resize using jQuery.
Here is my code:
// HTML Grid Container
<div id="myGridContainer" style="width:95%;">
<div class="grid-header" style="width:100%">
<label>ECO: Log / Slickgrid</label>
<span style="float:right" class="ui-icon ui-icon-search" title="Toggle search panel" onclick="toggleFilterRow1()"></span>
</div>
<div id="myGrid" style="width:100%;height:600px;"></div>
<div id="myPager"></div>
</div>
// My SlickGrid Options
var options = {
enableCellNavigation: true,
forceFitColumns: true
};
// The browser auto-resize
$(window).resize(function () {
$("#myGrid").width($("myGridContainer").width());
$(".slick-viewport").width($("#myGrid").width());
grid.resizeCanvas();
});
EDIT
I also was annoyed by the fact that using all of these together is blocking you from resizing the width of the column. I came up with a different solution, much later after, which makes the fields to expand (take available width) and does not block you afterwards on resizing the width. So this new solution I believe is giving you exactly what you are looking for... First of all remove the maxWidth property and only use minWidth and width, actually you could probably use only the width if you wanted. Now I had to unfortunately, modify 1 of the core file slick.grid.js with the following code:
//-- slick.grid.js --//
// on line 69 insert this code
autoExpandColumns: false,
// on line 1614 PREVIOUS CODE
if (options.forceFitColumns) {
autosizeColumns();
}
// on line 1614 change to this NEW CODE
if (options.forceFitColumns || options.autoExpandColumns) {
autosizeColumns();
}
then going back to my grid definition, I replace my previous options with this:
// My NEW SlickGrid Options
var options = {
enableCellNavigation: true,
forceFitColumns: false, // make sure the force fit is false
autoExpandColumns: true // <-- our new property is now usable
};
with this new change it has some functionality of the force fit (expanding) but does not restrict you on resizing your columns width afterwards like the force fit does. I also tested it with the columnPicker, if you hide a column it's resizing the others accordingly. I also modified the file slick.columnpicker.js to include a checkbox for that property but that is totally optional...I can add the code for that too if any of you want it as well. Voila!!! :)
EDIT #2
I realized much later that there's no need to modify the core file, we can simply call grid.autosizeColumns() after the grid creation. Like this
var options = { forceFitColumns: false };
grid = new Slick.Grid("#myGrid", data, columns, options);
// will take available space only on first load
grid.autosizeColumns();
This will automatically resize the columns to fit the screen on first load but will not give you the restriction of the forceFitcolumns flag.
I know it's kind late for this reply.
But i've managed to do that without having to change things at slick.grid.js or set min/maxWidth at columns array.
Instead what i did was to iterate through the columns array adding the values of "width" field of each column and then i've did a simple math count to set the last column width as innerWidth - totalColumsWidth + lastColumnWidth.
Code:
function lastColumnWidth(columns)
{
var widthSum = 0;
angular.forEach(columns, function(col) {
if(col.width) { widthSum = col.width + widthSum; }
});
if(window.innerWidth > widthSum) {
columns[columns.length-1].width = columns[columns.length-1].width + (window.innerWidth - widthSum);
}
return columns;
}