What is the correct way to toggling a filter on a json array? - html

I have an array of json objects, and I want to be able to show either all of them, or remove some of them by filtering them by a key value.
I achieved this by creating a new constant:
const filtered = data.filter((item) => item.highVariance === false);
And a different constant:
const showHighVar = isHighVar ? data : filtered;
And then I have a checkbox that lets me toggle the shotHighVar constant in:
input type="checkbox" onChange={() => setHighVar(!isHighVar)}/>
In order to map it later in code:
{sorted(showHighVar).slice(0, 25 * pageIndex).map((x) => (...))}
But it seems to me like there should be a better way of doing this, but I can't figure it out.

There is nothing wrong with the way you are doing it. the one thing that i would change is that instead of creating filtered variable all the time just filter the data when isHighVar is false. So your code should look something like this -
const showHighVar = isHighVar ? data : data.filter((item) => item.highVariance === false);
{sorted(showHighVar).slice(0, 25 * pageIndex).map((x) => ( .....))}
Or when you are running the map function after sorting and slicing. just add a if statement in map function and check if isHighVar is false then return null else do whatever you are doing.
eg.
{sorted(data).slice(0, 25 * pageIndex).map((x) => {
if(isHighVar===false && x.highVariance!==false){
return null;
}
....
})}

Related

Increasing Value of State Variable in ForEach loop for Progress Bar in React

I'm trying to figure out a way to build a progress bar with React. I have a forEach loop that iterates through an array of about 7,000 indexes. Each time I validate a row, I want to update a state variable with percentage completion (and render this on the page live). I've tried iterating through these indexes, and updating my state variable (hoping to update the page) in the loop but I'm realizing that will not work. I obviously can't do this with a normal variable as it will reset when the component re-renders. Can anyone give me some insight on this topic?
Thanks.
Here is a code snippet from what I'm looking at:
parsedAssets.forEach(asset => {
newAssetValidated = validateBulkUpload(asset, parsedAssets, assetList, accountLogged, jobSites);
!newAssetValidated.reject_err ? validatedAssetList.accepted.push(newAssetValidated) : validatedAssetList.rejected.push(newAssetValidated);
setStateAssets({ ...stateAssets, validatedAssetList });
});
}
So essentially, as each asset is either accepted or rejected we add it to "stateAssets", and I'm hoping to build the progress bar from the length of the combined arrays that are getting set in stateAssets. However, when the forEach loop is completed, only the last validated asset is getting set due to it not updating until the forEach loop is completed.
Personally I can't imagine such a heavy validation, that you need progress-bar, but anyway.
First solution is to separate validation itself from state update for progress-bar into separate "threads". But since JS is single threaded, you may use some tricks with setTimeout or setInterval functionality. It may be very tricky, and in general not recommended practice with React.
Another way is - to set the work into queue & process 1 item at a time.
As an example I would do it something like this:
function ComponentWithProgress({parsedAssets, setParsedAssets}) {
const [validatedAssetList, setValidatedAssetList] = useState([])
const [progress, setProgress] = useState(0)
const [toDo, setToDo] = useState([])
if(parsedAssets && parsedAssets.length>0) {
setToDo(parsedAssets)
// clear parsedAssets in parent component to: [], false, null ...
// so you put it into toDo only once
setParsedAssets([])
}
if(toDo.length > 0) {
const asset = toDo[0]
const newToDo = toDo.slice(1) // All but 0th element
const newAssetValidated = validateBulkUpload(asset);
setValidatedAssetList([ ...validatedAssetList, newAssetValidated ]);
setToDo(newToDo)
setProgress( newToDo.length / ( validatedAssetList.length + newToDo.length ) * 100 )
}
// ... Render here
// If you need only accepted
const accepted = validatedAssetList.filter(v => !v.reject_err)
}
This example maybe not work for you as is, because you didn't showed us the context, but the main idea is here.

Multiple Variable Assignment in Javascript/GAS - Is this the most compact way to do it?

Ok so I have a spreadsheet which we extract a 2d array of values from.
But really I want one variable per line of this 2d array.
The following code does work... but is this the best way to do it?
function testAssignments(){
config = ss.getRange("C2:C6").getValues();//2D Array
result = []
config.forEach(x => result.push(x[0]))
var [a,b,c,d,e] = result;
console.log(a,b,c,d,e);
}
I also tried the line config.forEach(x=> x=x[0]) but that didn't work for some reason.
Use .flat instead of .forEach and .push. If you want a different variable name for each element, there isn't a better way.
const [a,b,c,d,e] = ss.getRange("C2:C6").getValues().flat();//1D Array
//or
const [[a],[b],[c],[d],[e]] = ss.getRange("C2:C6").getValues();

Is there a way to sort a table based on a cell value in Angular?

My current table looks like this:
Status
Draft
Pending
Complete
I want to sort them based on the value of the cells. Is there a way to do that? I've only been able to sort them using this code:
onChange(status: string){
const sortState: Sort = {active: status, direction: 'desc'};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
}
But I want to sort using the values of the status themselves since I'd want to create a button which when click sorts starting from complete or draft or pending.
I'm a little confused by your question, but I think I understand what you're asking.
You're going to want to convert your values into an array and then use the .sort() function. So, assuming you have an array of your cells, we can call that let array = Cell[], you can then access the status of the cells like this:
sortCells(){
let array = Cell[]; // here we're assuming there is already a cell type and a cell.active parameter, like shown in your example.
let possibleValues = ["Draft","Pending","Complete"]; // easier way to compare two values
array.sort((a,b)=>{
let aIndex = possibleValues.indexOf(a.active); // index of gets the location of the element in an array
let bIndex = possibleValues.indexOf(b.active);
if(a > b){
return -1;
} else if(b > a){
return 1;
}else{
return 0; // they are equal
}
})
}
You can read more about sort here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Svelte virtual list component - function not working after filtering list

I am using the virtuallist component in a svelte project. I have added filtering to the list. My issue is that a function in my project stops working when I filter the list, I'm assuming because the list item is not yet in the dom when filtered?
The project converts medical units from metric units to international units using two inputs. Changing one input automatically converts the other.
Before filtering, everything works well with conversion but after entering a item name, (e.g. Type Zinc), the input conversion fails in the filtered items. No conversion occurs.
I've looked into afterUpdate as an option but not sure how to implement it.
---------Added Info -------------------
The issue is with list items not yet in view. Try typing "zinc" and then changing the input values of Zinc (fails) vs typing Acetone (item already in view) and changing those inputs (it works).
Here is a working REPL
The script:
<script>
import VirtualList from './VirtualList.svelte';
import unitsH from './data.js';
let searchTerm = "";
let start;
let end;
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm) !== -1);
function setBothFromSIH(value, i) {
const {factor, siValue} = unitsH[i];
unitsH[i].siValue = +value;
unitsH[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = unitsH[i];
unitsH[i].usValue = +value;
unitsH[i].siValue = +(value * factor).toFixed(2);
}
</script>
With simplified html code:
<VirtualList items={filteredList} bind:start bind:end let:item >
<div class="border" style="overflow-x: scroll;"> <div><div>
<div class="name">{item.name}</div>
<span>Specimen: {item.specimen} </span>
<span> Conversion Factor: {item.factor} </span>
</div>
<div>
<label>US Range:{item.conventionalRange} {item.conventionalUnit}</label>
<input name="us{filteredList.indexOf(item)}" value={item.usValue} on:input="{e => setBothFromUSH(e.target.value, filteredList.indexOf(item))}" type=number placeholder=" US">
</div>
<div>
<label>SI Range: {item.siRange} {item.siUnit}</label>
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, filteredList.indexOf(item))}" type=number placeholder="SI">
</div></div> </div>
</VirtualList>
<p>showing items {start}-{end}</p>
Thanks for any help in getting this to work!
It's a small issue with your filter. You convert the product name to lower case but not the filter term ;) If you enter acetone instead of Acetone, then it works. The fix:
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
Edit:
The issue with not calling the function for some filtered element is that you display the filteredList but still do the lookup on the unitsH list. Change it to this and it works:
function setBothFromSIH(value, i) {
const {factor, siValue} = filteredList[i];
filteredList[i].siValue = +value;
filteredList[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = filteredList[i];
filteredList[i].usValue = +value;
filteredList[i].siValue = +(value * factor).toFixed(2);
}
Happy hacking!
Your problem is caused by using the wrong index, in the change handler you pass the index of the item in filteredIndex but then you use that one to change the item on that index in the array unitsH.
You can see that by:
- start anew
- note the value for Acetaminophen (index 0)
- search zinc
- change value of zinc (index 0 in filtered list)
- clear search
->> acetaminophen has changed because that is index 0 in unitsH
You can easily solve this by passing in the index of the original array instead:
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, unitsH.indexOf(item))}" type=number placeholder="SI">
However, if you move the markup for each item to a seperate component you can vastly simplify this by directly interacting with the properties instead of trying to change them in the array.

How to add legend for a bar chart with different colors in dc.js?

Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.