What is the correct way to read an item's color from the Vega scenegraph? - vega-lite

I am rendering a graph using vega that has a custom color scheme. The graph also has a custom tooltip via vega-tooltip. I would like the tooltip to be the same color as the series which is being hovered over. This requires knowing the color of the feature that is being hovered.
The only thing that has worked so far is something like this:
let pathGroup = [];
const view = new vega.View(parsed, {
container: $el,
hover: true,
renderer: "canvas",
tooltip: new vt.Handler({
formatTooltip: (value, sanitize) => {
const myPath = pathGroup.filter((s: any) => s.datum.key === value.key)[0];
const color = myPath.items[0].items[0].stroke;
return `
<div id="vg-tooltip-inner">
<span id="vg-tooltip-inner-key" style="background: ${color}">
${sanitize(value.key)}
</span>
<span id="vg-tooltip-inner-value">
${sanitize(value.value)}
</span>
</div>
`;
},
}).call,
});
pathGroup = view.scenegraph().root.items[0].items.filter(
(item) => item.name === "layer_0_pathgroup"
)[0].items;
My original idea was to add a computed field to my tooltip encoding, but I wasn't able to figure out how to access stroke in that way. I also would prefer not to rely on hardcoded names like "layer_0_pathgroup", or positions in internal arrays.
Is there a cleaner or more appropriate way to access a property like this stroke?

Related

React range slider with two handles

I'm trying to make a range slider with two handles. Its main purpose is to filter the array of cars that I fetch from the server. Should I use library for the slider? I managed to almost get it done without any library. Here is the minimal working example:
https://codesandbox.io/s/trusting-poincare-4kzcvy?file=/src/Slider.js
I seem to get it wrong in regard to calculations, since the red part of rail doesn't follow up precisely when dragging a handle, and the min value matches the full max value if dragged to the end.
//destructive props
const RangeSlider = ({ classes, label, onChange, value, ...sliderProps }) => {
//set initial value to 0 this will change inside useEffect in first render also| or you can directly set useState(value)
const [sliderVal, setSliderVal] = useState(0);
// keep mouse state to determine whether i should call parent onChange or not.
// so basically after dragging the slider and then release the mouse then we will call the parent onChange, otherwise parent function will get call each and every change
const [mouseState, setMouseState] = useState(null);
useEffect(() => {
setSliderVal(value); // set new value when value gets changed, even when first render
}, [value]);
const changeCallback = (e) => {
setSliderVal(e.target.value); // update local state of the value when changing
}
useEffect(() => {
if (mouseState === "up") {
onChange(sliderVal)// when mouse is up then call the parent onChange
}
}, [mouseState])
return (
<div className="range-slider">
<p>{label}</p>
<h3>value: { sliderVal }</h3>
<input
type="range"
value={sliderVal}
{...sliderProps}
className={`slider ${classes}`}
id="myRange"
onChange={changeCallback}
onMouseDown={() => setMouseState("down")} // When mouse down set the mouseState to 'down'
onMouseUp={() => setMouseState("up")} // When mouse down set the mouseState to 'up' | now we can call the parent onChnage
/>
</div>
);
};
export default memo(RangeSlider);
I tried it this way but it doesnt add up to the solution when having two handles.

Getting "natural" display type for HTML elements?

I have a a web page where various fields are shown or hidden by toggling between "display:block" and "display:none". However, I added some extra stuff to the page and discovered that I needed to special-case several tags: TD needs to use "display:table-cell:, TR needs to use "display:table-row", and so on...
Is there any general off-the-shelf solution to this (i.e. look up the "natural" display type based on the tag name) or am I stuck with creating a JS object by hand which lists tag names and the corresponding display types?
You can apply revert to display property to set the element to it's original value and get the original value by Javascript.
const getOriginalDisplayProperty = (elem) => {
const modifiedDisplayProperty = getDisplayProperty(elem); //store modified display property
elem.style.display = "revert"; //revert to original display property
setTimeout(() => {
elem.style.display = modifiedDisplayProperty; // Again set original display property
}, 0);
return getDisplayProperty(elem);
};
const getDisplayProperty = (elem) =>
window.getComputedStyle(elem, null).display;
getOriginalDisplayProperty(document.querySelector(".test"));
getOriginalDisplayProperty(document.querySelector(".test-span"));
div {
display: inline;
}
span {
display: flex;
}
<div class="test"></div>
<span class="test-span"></span>

Set transparent for specific element

I want set transparent for specific element, i follow this code:
var instanceTree = this.viewer.model.getInstanceTree();
var fragList = this.viewer.model.getFragmentList();
this.listElement.forEach(element => {
instanceTree.enumNodeFragments(element, (fragId) => {
console.log(element.material)
var material = fragList.getMaterial(fragId)
if (material) {
material.opacity = value;
material.transparent = true;
material.needsUpdate = true;
}
});
});
this.viewer.impl.invalidate(true, true, true);
but it overide for all elements have that material. How can i set for selected element?
Appreciate any comments.
UPDATE 1:
i found go around way is clone main material than register it with different name:
var newMaterial = material.clone();
const materials = this.viewer.impl.matman();
materials.addMaterial('mymaterial',newMaterial,true);
fragList.setMaterial(fragId,newMaterial);
newMaterial.opacity = value;
newMaterial.transparent = true;
newMaterial.needsUpdate = true;
but effect is not what i want, it has different color and when set transparent i can only see a couple object behind it
You can create your own, custom THREE.js material and assign it to the fragment using fragList.setMaterial(fragId, material).
For more information on using custom materials or shaders, see https://forge.autodesk.com/blog/custom-shader-materials-forge-viewer.
EDIT:
Regarding the visual anomalies (for example, when you only see some of the objects behind something semi-transparent), this is a known problem, unfortunately with no clear solution. When the Forge Model Derivative service creates an SVF file to be viewed in Forge Viewer, the individual scene elements are stored in a data structure optimized for fast traversal, depending on whether they are semi-transparent or fully opaque. This data structure is fixed, and so unfortunately, when you take an object that was originally fully opaque, and you make it semi-transparent, it will most likely be rendered in a wrong order...

Custom Angular directive is not working with async value

We have a custom directive in our project which we use when we want trim the long text in some UI elements. In one case it fails to work. There are no errors, no warnings, it's just no there. Checking the code in the DevTools shows no signs of this directive triggering (no HTML changes, no CSS added). The directive looks like this:
ngAfterViewInit() {
let text = <string>this.elt.nativeElement.innerHTML.trim();
if (!text || text !== (<HTMLElement>this.elt.nativeElement).innerText) {
return;
}
const limit = this.value || DEFAULT_VISIBLE_ENDING_LENGTH; // default length = 4
if (text.length > limit && this.elt.nativeElement.scrollWidth > this.elt.nativeElement.clientWidth) {
const startText = text.substr(0, text.length - limit);
const endText = text.substr(-limit);
this.renderer.setProperty(
this.elt.nativeElement,
'innerHTML',
`<div class="part1"><span>${startText}</span></div><div class="part2"><span><span>${endText}</span></span></div>`
);
}
}
It fails to work when the text to display & trim is obtained from observable (store selector). It doesn't matter if I use Observable + async pipe or if I map the value to the component property in selector subscribe.
#Component({
...
changeDetection: ChangeDetectionStrategy.OnPush,
})
this.sampleInProgress$: Observable<string>;
this.sampleInProgress: string;
...
this.sampleInProgress$ = this.store.select(fromAutomation.getInfoPanelData).pipe(
map(({ sample, experiment }) => {
this.sampleInProgress = sample?.sampleName; // does not work either
this.experimentInProgress = experiment?.parameterSet;
return sample?.sampleName;
}),
);
And the HTML:
<span class="label" gs-ellipsis>{{ sampleInProgress$ | async }}</span>
<!-- In this case, subscribe is done in the component -->
<span class="label" gs-ellipsis>{{ sampleInProgress }}</span>
Sorry for the bit messy code, I just didn't wanted to post almost the same code twice. I'm either subscribing explicitly or assigning the observable using async with it. Not doing both at the same time. The other place in the code where we use this ellipsis (and where it works) also uses OnPush Detection strategy the but that the value is provided by #Input.
I have a feeling that it has something to do with the ngAfterViewInit() in the directive itself, but I'm not sure. Directives are not my strongest field.
Any idea what can be the cause and how to fix it?
your directive handling happends too early. I assume you can hack it a bit and render element just when its content is ok with the help of ngIf directive.
<span class="label" gs-ellipsis *ngIf="sampleInProgress$ | async as value">{{ value }}</span>

Get cursor position in a contenteditable div using innerHTML and pipes

I'm writing a simple WYSIWYG editor in Angular 5 to handle tags in the text. Those tags are like variables. For instance when doing: Hi (!--username--), welcome! it's rendered as Hi alex, welcome!. In order to be user-friendly for the non-technical, the WYSIWYG is transforming (!--username--) to a pretty HTML fragment showing directly "Alexandre" in its content.
This editor needs to handle simple HTML tags too (<b>, <i>, ...)
To do that, I've developed a component named editor which is using Angular's value accessors and showing a simple div like that:
<div class="editor" #editor [innerHTML]="content | prettytags: completions" (focus)="toogleToolbar()" (focusout)="toogleToolbar()"
(click)="onClick($event)" (keyup)="onKey($event)" [attr.contenteditable]="!readonly"></div>
The pipe looks like (for information, completions is the variable containing all tags values):
const pattern: RegExp = /(\(!--[^\s-]*--\))/;
#Pipe({
name: 'prettytags'
})
export class PrettyTagsPipe {
constructor(private sanitizer: DomSanitizer) {}
transform(value: string, completions: any[]): SafeHtml {
if (isNil(value)) return '';
const text = this.makeText(value, completions, 0);
return this.sanitizer.bypassSecurityTrustHtml(text);
}
private makeText(value: string, completions: any[], index: number): any {
const text = value
.split(pattern)
.map(word => {
const tag = completions.find(t => t.tag === word);
return isNil(tag)
? word
: this.getTagHtml(tag.value)
})
.join('');
return text;
}
private getTagHtml(text: any) {
return `<span class="chip" spellcheck="false">${text}</span> `;
}
}
In order to get the two-way data binding working as I'm using [innerHTML], I'm using the keyup event to get new characters but I need to get the caret position to append new characters. To do that I've copy/pasted a function found on Stack Overflow to get the caret position:
private getCaretPosition() {
const element = document.querySelector('.editor');
const range = window.getSelection().getRangeAt(0);
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
return preCaretRange.toString().length;
}
And on my onKeyUp: I do the following:
[...]
const position = this.getCaretPosition();
this.content += key.length === 1 ? this.content.slice(0, position) + key + this.content.slice(position) : '';
but it's not working as it gets the text position.
For instance, if the user wants to edit the content: from Hi (!--username--), welcome! to Hi (!--username--), I'm fine to see you back!, he will place his caret just after the comma, so I'll get 8 (for "Hi alex,") but with my content variable I'll get Hi (!--u.
I know I can get the position of the cursor with HTML tags, but I'll need to do many computations for each key pressed.
Do you have any idea to get this thing to work?