I'm drawing data in a JS canvas. I need to enter the veritcal and horizontal axis values at fix intervals.
I would ask how to create an array of paragraph or text to assign an absolute coordinate and the right axis value to draw them around the canvas.
currently I created and edited line by line:
document.getElementById('percent0').innerHTML =
a.toFixed(5-Math.round(Math.log10(a)));
a = minVal+(maxVal-minVal)*0.25;
document.getElementById('percent25').innerHTML =
a.toFixed(5-Math.round(Math.log10(a)));
a = minVal+(maxVal-minVal)*0.5;
document.getElementById('percent50').innerHTML =
a.toFixed(5-Math.round(Math.log10(a)));
a = minVal+(maxVal-minVal)*0.75;
document.getElementById('percent75').innerHTML =
a.toFixed(5-Math.round(Math.log10(a)));
a = minVal+(maxVal-minVal)*1;
document.getElementById('percent100').innerHTML =
a.toFixed(5-Math.round(Math.log10(a)));
but I would like to use a smart way to do it with array if possible.
thks.
Just add mark values to array and use it where it changes:
const marks = [0, 25, 50, 75, 100};
marks.forEach(mark => {
const a = minVal+(maxVal-minVal)*mark / 100.0;
document.getElementById('percent' + mark).innerHTML =
a.toFixed(5-Math.round(Math.log10(a)));
});
If you want to create those html elements with code too you can simple have something like <div id="marks-container;></div> in your HTML and then add all paragraps with JS too -
const p = document.createElement('p');
p.id = "percent" + mark;
document.querySelector("#marks-container").appendChild(p);
people of the internet!
I'm making a program that needs to try and find the closest RGB match of lots of pixels, using Jimp.
Say if you had a color that was slightly purple, I would want to change it to the purple that I have in my table, here:
var colors = [
[0,0,255,0],
[1,255,165,0],
[2,0,0,255],
[3,255,192,203],
[4,165,42,42],
[5,255,255,255],
[6,0,0,0],
[7,230,230,350],
[8,255,255,0],
[9,0,0,0],
]
(the first number in one of these arrays can be ignored, the rest are just R, G, and B)
So if I had a red like (255,7,1) I would like to match that to the red in my table, being (255,0,0)
I have tried something, but rather it was very dumb and didn't work, so I will spare you the details.
Can anybody help me? Thanks!
RGB is a 3-dimensional space. Consider a color as a point in 3d space, you should find most closest distance between two points by using 3d pythagoras.
const colors = [
[0,0,255,0],
[1,255,165,0],
[2,0,0,255],
[3,255,192,203],
[4,165,42,42],
[5,255,255,255],
[6,0,0,0],
[7,230,230,350],
[8,255,255,0],
[9,0,0,0],
]
function get_closest_color(colors, [r2, g2, b2]) {
const [[closest_color_id]] = (
colors
.map(([id, r1,g1,b1]) => (
[id, Math.sqrt((r2-r1)**2 + (g2-g1)**2 + (b2-b1)**2)]
))
.sort(([, d1], [, d2]) => d1 - d2)
);
return colors.find(([id]) => id == closest_color_id);
}
const closest_color = get_closest_color(colors, [230, 200,0]);
console.log(closest_color);
If I undestand your question correctly you can do something like this
const colors = [
[0,0,255,0],
[1,255,165,0],
[2,0,0,255],
[3,255,192,203],
[4,165,42,42],
[5,255,255,255],
[6,0,0,0],
[7,230,230,350],
[8,255,255,0],
[9,0,0,0],
]
const findClosestColor = (color, colors) =>
colors.reduce((res, c) => {
const distance = [1,2,3].reduce((sum, i) => sum + Math.abs(c[i] - color[i]), 0)
if(distance > res.distance){
return res;
}
return {
closest: c,
distance
}
}, {closest: null, distance: 9999} ).closest
console.log(findClosestColor([0, 255,7,1], [[1,0,0,200], [2,255,0,0], [3, 128,128,128]]))
Use the function(s) from here to find the color with the smallest E distance
Color difference/similarity% between two values with JS
I am using ag-grid, I want the columns width to be dynamic according to the grid content,
I do it by using:
const onGridReady = params => {
params.columnApi.autoSizeAllColumns();
};
but the width of the grid is alays fixed, I have a space in the side of the grid.
(I can't also send width to the grid, because I can't know what will be the size of the content)
what I need is something how to combine autoSizeAllColumns and sizeColumnsToFit functions.
ag-grid has a property called defaultColDef that can be used for grid settings. If you pass flex: 1 as one of the parameters then all columns will size to fit so that you won't have that empty space on the side (expands to fill). Check out the ag-grid documentation on this page and search for the work "flex" for more details on auto/flex sizing.
If I understand you correctly you want to have the following behaviour for resizing - make sure each column has it's content visible first, but then also make sure that the whole grid width is filled at the least.
Unfortunately there doesn't seem to be a really straightforward way to achieve that. What I did is onGridReady, I would use the autoSizeColumns function to make sure that each column's content is fully visible, and then if there is an additional space left to fill the grid's width I distribute it evenly to each column. Then apply the new column state through gridApi.applyColumnState. Here is an example in vue
that should be fairly easy to transfer to other frameworks (or vanilla js).
interface UseGridColumnResizeOptions {
// we need access to the grid container so we can calculate
// the space that is left unfilled.
gridContainerRef: Ref<null | HTMLElement>;
// for any columns that you don't want to resize for whatever reason
skipResizeColumnIds: string[];
}
export const useGridColumnResize = ({ gridContainerRef, skipResizeColumnIds }: UseGridColumnResizeOptions) => {
const handleResize = ({ columnApi }: AgGridEvent) => {
columnApi.autoSizeAllColumns();
if (!gridContainerRef.value) {
console.warn('Unable to resize columns, gridContainer ref is not provided');
return;
}
const isColumnResizable = (colDef: ColDef) => colDef.resizable && !skipResizeColumnIds.includes(colDef.colId!);
const columns = columnApi.getAllGridColumns().filter((column) => isColumnResizable(column.getColDef()));
if (columns.length === 0) {
return;
}
const lastColumn = columns[columns.length - 1];
const lastColumnLeft = lastColumn.getLeft();
const lastColumnWidth = lastColumn.getActualWidth();
const { width: gridWidth } = gridContainerRef.value.getBoundingClientRect();
const gridSpaceLeftToFill = Math.max(0, gridWidth - (lastColumnLeft! + lastColumnWidth));
if (gridSpaceLeftToFill === 0) {
return;
}
const additionalSpaceForEachColumn = gridSpaceLeftToFill / columns.length;
const columnState = columnApi.getColumnState();
columnState.forEach((column) => {
const skipResizeForColumn = !columns.some((col) => col.getColId() === column.colId);
if (skipResizeForColumn) {
return;
}
column.width = column.width! + additionalSpaceForEachColumn;
});
columnApi.applyColumnState({ state: columnState });
};
return { handleResize };
};
You can plug the handleResize function on row-data-updated event to resize columns whenever new data arrives in the grid or only once in grid-ready or first-data-rendered.
Keep in mind that this implementation plays out well in my case as columns are not movable. I am expecting the last column inside the columns array to be the last visible one in the UI, but that might not always be the case and you might end up with wrong calculation of the space that is left to fill. So you might need to change the way the last visible column in the UI is retrieved to make it work for your case.
I have a button and an image.
When I click the button, I want it to go to a "wait mode" of sorts.
Waiting for two separate clicks, that both return the x, y value of the mouse click events.
I got the mouse xy part no problem but at loss for what RxJS operator to use next
const elem = document.getElementById("MyImage");
const root = fromEvent(elem, "click");
const xy = root.pipe(map(evt => xyCartoPos(elem, evt)));
xy.subscribe(coords => console.log("xy:", coords));
function xyCartoPos(elem, e) {
const elemRect = elem.getBoundingClientRect();
return {
x: e.clientX - elemRect.x - elemRect.width / 2,
y: flipNum(e.clientY - elemRect.y - elemRect.height / 2)
};
}
You can use bufferCount to emit a fixed number of clicks at once (in one array).
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
bufferCount(2),
//take(1) // use take(1) if you only want to emit one pair of clicks and then complete
);
You could use scan to collect the events as an array, then use filter to verify the length of the array is 2:
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
scan((acc, evt) => {
acc.push(evt);
return acc;
}, []),
filter(events => events.length == 2),
);
This will cause only an array with the two mouse events, after two clicks, to be published to the subscriber.
I've got a chart with some random data. On button click I'd like to add another chart on top of the first one. That works fine. I've also included zoom and it works fine with only the first chart. Zooming with both charts somehow copies the data from the second chart to the first one.
Have a look at the example. You should be able to see the blue chart. Click the button to add the green one. Now try to zoom in. The blue one disappears. However it is not gone. It is simply hidden behind the green one. They are perfectly on top of each other although they should have different values.
https://codesandbox.io/s/31lz6zrln5
Any ideas?
Best regards,
Mirco
In the button callback you modify the data elements.
filtered = () => {
const values = [...data].map(d => {
d.value = rnd(25, 75);
return d;
});
this.chart.filtered(values);
};
You should return new objects based on the fields of the other objects
filtered = () => {
const values = [...data].map(d => {
return { timestamp: d.timestamp, value: rnd(25, 75) };
});
this.chart.filtered(values);
};
Also update the filtered path in the zoom callback
public zoom = () => {
const newXScale = event.transform.rescaleX(this.xScale);
const newYScale = event.transform.rescaleY(this.yScale);
this.xAxisGroup.call(this.xAxis.scale(newXScale));
this.yAxisGroup.call(this.yAxis.scale(newYScale));
this.xGridGroup.call(this.xGrid.scale(newXScale));
this.yGridGroup.call(this.yGrid.scale(newYScale));
this.line.x(d => newXScale(d.timestamp)).y(d => newYScale(d.value));
this.lineGroup.attr("d", this.line as any);
this.lineFiltered.attr("d", this.line as any); // removed the comment of this line
};