The custom marker of the lightningchart doesn't work properly - lightningchart

I am using the marker from current example but with vertical oriented chart and a few my upgrades. So my problem that in case of vertical chart the labels and values of variables isn't shown. But the same logic is properly works with horizontal oriented chart.
private createCustomMarker(): void {
if (!this.seriesInstances.length) return;
const resultTable: UIElementColumn<UIBackground> = this.chartInstance
.addUIElement(UILayoutBuilders.Column, {
x: this.chartInstance.getDefaultAxisX(),
y: this.chartInstance.getDefaultAxisY()
})
.setMouseInteractions(false)
.setOrigin(UIOrigins.LeftCenter)
.setMargin(5);
const datetimeRow: UITextBox<UIBackground> = resultTable
.addElement(UILayoutBuilders.Row)
.addElement(UIElementBuilders.TextBox);
const rowsY: UITextBox<UIBackground>[] = this.seriesInstances
.map((el: ISeriesInstance, i: number) => {
return resultTable
.addElement(UILayoutBuilders.Row)
.addElement(UIElementBuilders.TextBox)
.setTextFillStyle(this.seriesInstances[i].instance.getStrokeStyle().getFillStyle());
});
const tick: CustomTick = (this.isAppearanceHorizontal ? this.chartInstance.getDefaultAxisX() : this.chartInstance.getDefaultAxisY())
.addCustomTick()
.setAllocatesAxisSpace(false)
.disposeMarker();
// Hide custom cursor components initially.
resultTable.dispose();
tick.dispose();
this.chartInstance.onSeriesBackgroundMouseMove((_: ChartXY<PointMarker, UIBackground>, event: MouseEvent): void => {
const mouseLocationClient: { x: number; y: number } = { x: event.clientX, y: event.clientY };
const mouseLocationEngine: Point = this.chartInstance.engine.clientLocation2Engine(
mouseLocationClient.x,
mouseLocationClient.y
);
// Translate mouse location to LCJS coordinate system for solving data points from series, and translating to Axes.
// Translate mouse location to Axis.
const mouseLocationAxis: Point = translatePoint(
mouseLocationEngine,
this.chartInstance.engine.scale,
this.seriesInstances[0].instance.scale
);
// Solve the nearest data point to the mouse on each series.
const nearestDataPoints: CursorPoint<Series2D>[] = this.seriesInstances.map((el: ISeriesInstance) => {
return el.instance.solveNearestFromScreen(mouseLocationEngine) // on this line the most of elements have undefined, but data for it exists and poits are near beetween each other
});
// console.log(nearestDataPoints);
// Find the nearest solved data point to the mouse.
const nearestPoint: CursorPoint<Series2D> = nearestDataPoints.reduce((prev: CursorPoint<Series2D>, curr: CursorPoint<Series2D>) => {
if (!prev) return curr;
if (!curr) return prev;
if (this.isAppearanceHorizontal) {
return Math.abs(mouseLocationAxis.y - curr.location.y) < Math.abs(mouseLocationAxis.y - prev.location.y) ? curr : prev;
} else {
return Math.abs(mouseLocationAxis.x - curr.location.x) < Math.abs(mouseLocationAxis.x - prev.location.x) ? curr : prev
}
});
if (nearestPoint) {
// Set custom cursor location.
resultTable.setPosition({
x: mouseLocationAxis.x,
y: mouseLocationAxis.y,
});
// Change origin of result table based on cursor location.
let resultTableOrigin;
const yScale: number = this.chartInstance.engine.scale.y.getInnerInterval();
const isResultTableOriginXRight: boolean = mouseLocationEngine.x > this.chartInstance.engine.scale.x.getInnerInterval() / 2;
if (mouseLocationEngine.y > yScale - (yScale / 100 * 30)) { // mouseLocationEngine.y > yScale - 30%
resultTableOrigin = isResultTableOriginXRight ? UIOrigins.RightTop : UIOrigins.LeftTop;
} else if (mouseLocationEngine.y < yScale / 100 * 30) { // mouseLocationEngine.y > 30% of yScale
resultTableOrigin = isResultTableOriginXRight ? UIOrigins.RightBottom : UIOrigins.LeftBottom;
} else {
resultTableOrigin = isResultTableOriginXRight ? UIOrigins.RightCenter : UIOrigins.LeftCenter;
}
resultTable.setOrigin(resultTableOrigin);
// Format result table text.
const datetimeValue = this.isAppearanceHorizontal
? this.chartInstance.getDefaultAxisX().formatValue(nearestPoint.location.x)
: this.chartInstance.getDefaultAxisY().formatValue(nearestPoint.location.y)
datetimeRow.setText(`${datetimeValue}`);
rowsY.map((rowY: UITextBox<UIBackground>, i: number) => {
// this.seriesInstances[i].instance.isDisposed() ? rowY.dispose() : rowY.restore(); after this line labels of the table is low font contrast
if (nearestDataPoints[i]?.location) {
const foundSeries = chain(this.track.series)
.flatMap()
.value()[i]
const value: string = this.isAppearanceHorizontal
? this.chartInstance.getDefaultAxisY().formatValue(nearestDataPoints[i].location.y)
: this.chartInstance.getDefaultAxisX().formatValue(nearestDataPoints[i].location.x)
rowY.setText(`${this.seriesInstances[i].instance.getName()}: ${value} ${foundSeries.unit}`) // probleblem on this line
}
});
tick.setValue(
this.isAppearanceHorizontal
? nearestPoint.location.x
: nearestPoint.location.y
);
resultTable.restore();
tick.restore();
} else {
resultTable.dispose();
tick.dispose();
}
});
this.chartInstance.onSeriesBackgroundMouseLeave(() => {
resultTable.dispose();
tick.dispose();
});
this.chartInstance.onSeriesBackgroundMouseDragStart(() => {
resultTable.dispose();
tick.dispose();
});
}
I want to understand why almost the same code don't work on vertical oriented chart, but on horizontal works good.

Related

How can I change the extent array in vue3-openlayer dynamically

I have several historical maps (around 8kx8k) that I display with a vue3 component. the image and the width and height are passed to these components.
<MapFlex :imgurl="content.maps[5].karte" :width="width" :height="height"></MapFlex>
for this I used the ol-source-image-static
that works quite well so far. The maps are more square, the area for the card is a landscape format rectangle. I now want to show this map fully visible when opened. that works if I don't set an extent in the view.
when I use the extent [-width/2, -height/2, width/2, height/2] I can no longer move the image out of the display field. but I can't make it smaller either. the map fills the view area.
if I want to change the extent rect dynamically depending on the zoom, there is a feedback loop, the system becomes lame and at some point it also stops.
What I actually want to do is that I switch between different extents when changing the zoom.
Or is there a function with which I would like to dynamically generate the appropriate extent. It is important that the map is visible in a smaller size in the display area and can then be zoomed in without being able to slide the map out of the area.
<template>
<div class="map-container">
<ol-map :loadTilesWhileAnimating="true" style="height:980px" >
<ol-view ref="view" :center="center" :rotation="rotation" :zoom="zoom" :maxZoom="maxZoom" :minZoom="minZoom" :projection="projection" :extent="extent" #zoomChanged="onZoomChanged" #resolutionChanged="onResolutionChanged"/>
<ol-image-layer>
<ol-source-image-static :url="imgUrl" :imageSize="size" :imageExtent="imageExtent" :projection="projection"></ol-source-image-static>
</ol-image-layer>
</ol-map>
</div>
</template>
<script>
import { ref, reactive} from 'vue'
export default {
props: {
imgurl: {
type: String,
default: "http://localhost:8080/karten/map-1.jpg",
},
width: {
type: Number,
default: 1024
},
height: {
type: Number,
default: 968
}
},
setup(props) {
const zoom = ref(2)
const minZoom = ref(1)
const maxZoom = ref(6)
const rotation = ref(0)
const faktor = 2
const size = ref([props.width, props.height])
const center = ref( [size.value[0]/2, size.value[1]/2] ); // ref ([0,0]) //
const imageExtent = ref( [-1*props.width/2, -1*props.height/2 , props.width/2, props.height/2 ] )
const extent = ref( [-1 * props.width / faktor, -1 * props.height / faktor, props.width / faktor, props.height / faktor] ) // number[leftBottomX, leftBottomY, rightTopX, rightTopY]
const currentZoom = ref(zoom.value);
const emitter = useEmitter();
console.log(extent);
const projection = reactive({
code: 'xkcd-image',
units: 'pixels',
extent: extent,
const onZoomChanged = (val) => {
currentZoom.value = val;
if (val < 3){
extent.value = [-1 * props.width / 1.5, -1 * props.height / 1.5, props.width /1.5, props.height / 1.5]
}
extent.value = [-1 * props.breite / 1.2, -1 * props.hoehe / 1.2, props.breite / 1.2, props.hoehe / 1.2]
}
const onResolutionChanged = () => {
// console.log("onResolutionChanged");
}
return {
center,
projection,
zoom,
minZoom,
maxZoom,
rotation,
size,
imageExtent,
extent,
imgUrl,
onZoomChanged,
onResolutionChanged,
}
},
}
</script>

slideshow stuck framer motion

I want to make a slide show in framer motion and I found that in framer motion docs they have an example slide show like this https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?from-embed=&file=/src/Example.tsx, but I found a bug when we drag and double click it, it will be stuck like this picture .
import * as React from "react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import { images } from "./image-data";
const variants = {
enter: (direction: number) => {
return {
x: direction > 0 ? 1000 : -1000,
opacity: 0
};
},
center: {
zIndex: 1,
x: 0,
opacity: 1
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 1000 : -1000,
opacity: 0
};
}
};
const swipeConfidenceThreshold = 10000;
const swipePower = (offset: number, velocity: number) => {
return Math.abs(offset) * velocity;
};
export const Example = () => {
const [[page, direction], setPage] = useState([0, 0]);images.
const imageIndex = wrap(0, images.length, page);
const paginate = (newDirection: number) => {
setPage([page + newDirection, newDirection]);
};
return (
<>
<AnimatePresence initial={false} custom={direction}>
<motion.img
key={page}
src={images[imageIndex]}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
opacity: { duration: 0.2 }
}}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(1);
} else if (swipe > swipeConfidenceThreshold) {
paginate(-1);
}
}}
/>
</AnimatePresence>
</>
);
};
I try to solve this problem but still can't fix it, can someone help me?
This looks like a bug of framer-motion.
Up until v1.6.2, everything works fine. The bug seems to occur in all later versions.
There is also an interesting changelog:
[1.6.3] 2019-08-19
Fixed
Ensuring onDragEnd always fires after if onDragStart fired.
Here is a link to the related issue on GitHub, opened by the author of this question.
Until that bug is fixed, here is a workaround that uses Pan events
export default function Carousel() {
const animationConfidenceThreshold = 200; // you have to move the element 200px in order to perform an animation
const [displayed, setDisplayed] = useState(0); // the index of the displayed element
const xOffset = useMotionValue(0); // this is the motion value that drives the translation
const lastOffset = useRef(0); // this is the lastValue of the xOffset after the Pan ended
const elementAnimatingIn = useRef(false); // this will be set to true whilst a new element is performing its animation to the center
useEffect(() => {
// this happens after we have dragged the element out and triggered a rerender
if (elementAnimatingIn.current) {
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
// if the element has animated out to the right it animates in from the left
xOffset.set(
rightPan ? -1 * window.innerWidth - 200 : window.innerWidth + 200
);
// perform the animation to the center
animate(xOffset, 0, {
duration: 0.5,
onComplete: () => {
xOffset.stop();
},
onStop: () => {
elementAnimatingIn.current = false;
lastOffset.current = xOffset.get();
}
});
}
});
return (
<div className="container">
<motion.div
className="carouselElement"
onPan={(e, info) => {
xOffset.set(lastOffset.current + info.offset.x); // set the xOffset to the current offset of the pan + the prev offset
}}
style={{ x: xOffset }}
onPanStart={() => {
// check if xOffset is animating, if true stop animation and set lastOffset to current xOffset
if (xOffset.isAnimating()) {
xOffset.stop();
lastOffset.current = xOffset.get();
}
}}
onPanEnd={(e, info) => {
// there can be a difference between the info.offset.x in onPan and onPanEnd
// so we will set the xOffset to the info.offset.x when the pan ends
xOffset.set(lastOffset.current + info.offset.x);
lastOffset.current = xOffset.get(); // set the lastOffset to the current xOffset
if (Math.abs(lastOffset.current) < animationConfidenceThreshold) {
// if its only a small movement, animate back to the initial position
animate(xOffset, 0, {
onComplete: () => {
lastOffset.current = 0;
}
});
} else {
// perform the animation to the next element
const rightPan = xOffset.get() > 0; // check if the user drags it to the right
animate(
xOffset,
rightPan ? window.innerWidth + 200 : -1 * window.innerWidth - 200, // animate out of view
{
duration: 0.5,
onComplete: () => {
// after the element has animated out
// stop animation (it does not do this on its own, only one animation can happen at a time)
xOffset.stop();
elementAnimatingIn.current = true;
// trigger a rerender with the new content - now the useEffect runs
setDisplayed(rightPan ? displayed - 1 : displayed + 1);
}
}
);
}
}}
>
<span style={{ userSelect: "none" }}>
{"I am element #" + displayed}
</span>
</motion.div>
</div>
);
}
Check this codesandbox out!

Logic in if else statement to divide path if path shape is two or more '0'

Hi all trying to write some logic in paper.js (also using opentype.js for font data) so that when a number contains two or more consecutive zeros' the zero path is divided so that it is solid.
Things i know a zero path, using my particular font, is made up of an outer path with 19 segments and an inner path made up of 18 segments
So I thought would try to iterate over all paths check if a path has 19 segments and the next path has 18 segments and call path.unite() which kind of works. But I only want it to do this with consecutive '0' eg '100', '1000' but not 10.
So i was trying to do an if else statment where if-else (the current path has 18 segments and the next path is less than 18 segments) if true then do nothin or call path.divide()?
Im sure there is a way better way of doing this. Can you help please.
link to codepen
paper.install(window);
window.onload = () => {
paper.setup("canvas");
opentype.load(
"https://assets.codepen.io/1070/pphatton-ultralight-webfont.woff",
(err, font) => {
if (err) {
console.log(err);
} else {
const fontPath = font.getPath("10k", 0, 100, 100).toSVG();
const count = new paper.CompoundPath(fontPath);
count.unite();
count.children.forEach((child, i) => {
if (
child.segments.length === 19 &&
count.children[i + 1].segments.length === 18
) {
const eye = child.unite();
eye.selected = true;
} else if(
count.children[i + 1].segments.length === 18
&& child.segments.length < 18
) {
console.log('hello');
// const target = child.divide();
count.children[i].fillColor = 'black'
} else{
}
});
// const flatCount = count.children[1].unite()
// console.log(count.children[2].segments.length)
// const flatCountTwo = count.children[5].unite()
// flatCount.translate(5,0)
count.fillColor = "red";
project.activeLayer.fitBounds(view.bounds.scale(0.6));
}
}
);
};
I think that rather than using Font.getPath to retrieve a single svg path for the whole text, you should use Font.getPaths to retrieve an svg path for each character.
This way you can quite simply do your analysis on the input string directly and handle the consecutive 0 differently than other characters.
Edit
In order to detect the consecutive zeros, yes, you could use a regex or loop over the characters, like I did in the following example.
Here's a fiddle showcasing a possible solution.
const handleZero = (path) => {
path.children = path.children.slice(0, 1);
};
const getConsecutiveZerosIndices = (content) => {
const zero = '0';
return [...content]
.map((char, i) => ({ char, i }))
.filter(({ char, i }) => {
const previousCharacter = content?.[i - 1];
const nextCharacter = content?.[i + 1];
return char === zero && (previousCharacter === zero || nextCharacter === zero);
})
.map(({ i }) => i);
};
const drawText = (content, font) => {
const fontPaths = font.getPaths(content, 0, 100, 100);
const consecutiveZerosIndices = getConsecutiveZerosIndices(content);
const paths = fontPaths.map((fontPath, i) => {
const path = new paper.CompoundPath(fontPath.toSVG());
if (consecutiveZerosIndices.includes(i)) {
handleZero(path);
}
return path;
});
const group = new paper.Group(paths);
group.fillColor = 'red';
return group;
};
const draw = (font) => {
const path1 = drawText('10k', font);
const path2 = drawText('100k', font);
const path3 = drawText('1000k', font);
path2.position = path1.position.add(0, path1.bounds.height * 1.2);
path3.position = path2.position.add(0, path2.bounds.height * 1.2);
paper.project.activeLayer.fitBounds(paper.view.bounds.scale(0.6));
};
paper.setup('canvas');
opentype.load(
'https://assets.codepen.io/1070/pphatton-ultralight-webfont.woff',
(err, font) => draw(font)
);

Apply CSS Filters to cropped image and save/upload

I can already, input the image and crop it. I tried to apply CSS filters to it, but seems the CSS filters only apply on the img tag, not the actual image.
I am using both #Alyle-cropping and ngx-image-cropper(tests). Both give to me a base64 string for the cropped image. I am able to load the cropped image to the img tag and also upload it to the database.
onCropped(e: ImgCropperEvent) {
this.croppedImage = e.dataURL;
// console.log('cropped img: ', e.dataURL);
}
onloaded(e: ImgCropperEvent) {
this.imagemOriginal = e.originalDataURL;
this.cropper.center();
console.log('img loaded', e.name);
}
onerror(e: ImgCropperErrorEvent) {
console.warn(`'${e.name}' is not a valid image`, e);
}
// Aplicar Filtros /////////////////////////////////////////////////
change(crop: Crop): void {
this.stylus = crop.nome;
this.crops.forEach(function (value) {
(value.nome === crop.nome) ? value.ehSelec = true : value.ehSelec = false;
});
// const canvas = document.getElementById('cropping'), image = document.createElement('img');
// image.src = canvas.toDataURL('image/jpeg', 1.0);
// document.body.appendChild(image);
}
enviarParanue(): void {
const ref = firebase.storage().ref(`imagens/usuarios/idTeste`).child(`nomeTeste`);
const stringa = this.removerString(this.croppedImage);
ref.put(this.base64toBlob(stringa, 'image/png')).then((snapshot) => {
// console.log('snapshot', snapshot.valueOf());
ref.getDownloadURL().then(function(downloadURL) {
console.log('File available at', downloadURL);
});
});
// ref.putString(stringa, 'base64', {contentType: 'image/png'}).then((snapshot) => {
// // console.log('snapshot', snapshot.valueOf());
// ref.getDownloadURL().then(function(downloadURL) {
// console.log('File available at', downloadURL);
// });
// });
}
removerString(stringa: string): string {
return stringa.substring(23);
}
base64toBlob(base64Data: any, contentType: any) {
contentType = contentType || '';
const sliceSize = 1024;
const byteCharacters = atob(base64Data);
const bytesLength = byteCharacters.length;
const slicesCount = Math.ceil(bytesLength / sliceSize);
const byteArrays = new Array(slicesCount);
for (let sliceIndex = 0; sliceIndex < slicesCount; ++ sliceIndex) {
const begin = sliceIndex * sliceSize;
const end = Math.min(begin + sliceSize, bytesLength);
const bytes = new Array(end - begin);
for (let offset = begin, i = 0 ; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return new Blob(byteArrays, { type: contentType });
}
EXAMPLE OF THE CSS FILTERS:
.none {filter:none;}
.blur {filter:blur(2.5px);}
.brightness {filter:brightness(200%);}
.contrast {filter:contrast(200%);}
.drop-shadow {filter:drop-shadow(8px 8px 10px gray);}
.grayscale {filter:grayscale(100%);}
.hue-rotate {filter:hue-rotate(90deg);}
.invert {filter:invert(100%);}
.opacity {filter:opacity(30%);}
.saturate {filter:saturate(8);}
.sepia {filter:sepia(100%);}
.contrast-brightness {filter:contrast(200%) brightness(150%);}
Problem is... I don't know how to apply the CSS filters to the image to upload the cropped with the effects(sepia, contrast, etc).
I tried to get the img src and convert it to Blob, but didn't work.
I ended up saving in the database a string with the name of the filter. So I apply the filter when I load the image. A good side of it, is that I can change the filter whenever I want.

Rendering components only if they can fit inside a flex container

I am trying to build a toolbar that hides components from the right if there is not enough space to render them. My approach is to use refs and add up the width and render based on the condition if the total width has been overflowed. I want to get something working and go on improving it from there. It seems to work 'ok' when the screen size is decreased but not when trying to 're-render' the components when there is room. I suspect adding a display style of 'none' is causing some of the issues.
componentDidMount() {
this.littleFunction();
window.addEventListener('resize', this.littleFunction);
}
littleFunction = () => {
let sofar = 0;
for (const ref in this.refs) {
sofar += this.refs[ref].offsetWidth;
const index = ref.indexOf('test');
console.log(ref, sofar, this.input.offsetWidth);
if (sofar > this.input.offsetWidth && index === -1) {
this.refs[ref].style.display = 'none';
}
// // console.log(typeof this.refs[ref].style.display, this.refs[ref].style.display);
// if (this.refs[ref] !== this.input) {
// sofar = this.refs[ref].offsetWidth + sofar;
// }
// const index = ref.indexOf('test');
// // console.log(sofar, this.input.offsetWidth, index);
// if (sofar >= this.input.offsetWidth && index === -1) {
// this.refs[ref].style.display = 'none';
//
// this.forceUpdate();
// } else if (sofar < this.input.offsetWidth && index === -1) {
// // console.log('inhiaaa', sofar, this.input.offsetWidth);
// this.refs[ref].style.display = '';
//
// this.forceUpdate();
// }
}
}
After thinking about this for a while, i realized that if i set the style to display: 'none', the next time I try to run this logic to check how many components can fit, I am actually not getting the length back from the components that were previously set to display: 'none'. What I did was save the width of the components before applying calling the function.
componentDidMount() {
this.widths = new List();
for (const ref in this.refs) {
this.widths = this.widths.push(Map({
name: ref,
length: this.refs[ref].offsetWidth,
}));
}
this.littleFunction();
window.addEventListener('resize', this.littleFunction);
}
littleFunction = () => {
let sofar = 0;
this.widths.forEach(item => {
sofar += item.get('length');
const index = item.get('name').indexOf('test');
if (sofar > this.input.offsetWidth && index === -1) {
this.refs[item.get('name')].style.display = 'none';
// this.forceUpdate();
} else if (index === -1) {
this.refs[item.get('name')].style.display = 'inline';
// this.forceUpdate();
}
});
}
Have the toolbar to have style { width: '100%', overflow: 'hidden', whiteSpace: 'nowrap' } should give you the desired effect.

Resources