After restore of disposed UITextBox the text is pale - javascript

I have the logic of disposing of lines in my chart and have a custom cursor that I get from this link and when I dispose the line the label shouldn't show too and it works, but after restoring the rowY the names in textbox is pale, look at the next screenshots pale labels image, normal labels before disposing
rowsY.map((rowY, i) => {
this.seriesInstances[i][1].isDisposed() ? rowY.dispose() : rowY.restore();
if (nearestDataPoints[i]?.location?.y) {
rowY.setText(`${this.seriesInstances[i][1].getName()}: ${+this.chartInstance.getDefaultAxisY().formatValue(nearestDataPoints[i].location.y)} ${this.seriesInitialData[i].unit}`)
}
});

It seems you dispose/restore rowY in onSeriesBackgroundMouseMove.
I'd suggest to do that in the place where you dispose/restore the seriesInstances but still it will not work with legend box.
To fix it you can just dispose RowsY and RowX instead of resultTable and restore it after check:
rowX.restore()
series.forEach((el, i)=>{
//check if series was disposed
if(!el.isDisposed()){
rowsY[i].restore()
}
})
Also, in v.4.0 we will add new API that replaces dispose/restore
Here is updated code from the example that you use
// Import LightningChartJS
const lcjs = require("#arction/lcjs");
// Import data-generators from 'xydata'-library.
const { createProgressiveTraceGenerator } = require("#arction/xydata");
// Extract required parts from LightningChartJS.
const {
lightningChart,
AutoCursorModes,
UIElementBuilders,
UILayoutBuilders,
UIOrigins,
translatePoint,
Themes,
} = lcjs;
// Create a XY Chart.
const chart = lightningChart()
.ChartXY({
theme: Themes.lightNew,
})
// Disable native AutoCursor to create custom
.setAutoCursorMode(AutoCursorModes.disabled)
.setTitle('Custom Cursor using LCJS UI')
// set title for Y axis
chart.getDefaultAxisY().setTitle('Y-axis')
// generate data and creating the series
const series = new Array(3).fill(0).map((_, iSeries) => {
const nSeries = chart.addLineSeries({
dataPattern: {
// pattern: 'ProgressiveX' => Each consecutive data point has increased X coordinate.
pattern: 'ProgressiveX',
},
})
createProgressiveTraceGenerator()
.setNumberOfPoints(200)
.generate()
.toPromise()
.then((data) => {
return nSeries.add(data)
})
return nSeries
})
// Add Legend.
const legend = chart.addLegendBox().add(chart)
// Create UI elements for custom cursor.
const resultTable = chart
.addUIElement(UILayoutBuilders.Column, {
x: chart.getDefaultAxisX(),
y: chart.getDefaultAxisY(),
})
.setMouseInteractions(false)
.setOrigin(UIOrigins.LeftBottom)
.setMargin(5)
.setBackground((background) =>
background
// Style same as Theme result table.
.setFillStyle(chart.getTheme().resultTableFillStyle)
.setStrokeStyle(chart.getTheme().resultTableStrokeStyle),
)
const rowX = resultTable.addElement(UILayoutBuilders.Row).addElement(UIElementBuilders.TextBox)
const rowsY = series.map((el, i) => {
return resultTable
.addElement(UILayoutBuilders.Row)
.addElement(UIElementBuilders.TextBox)
.setTextFillStyle(series[i].getStrokeStyle().getFillStyle())
})
const tickX = chart.getDefaultAxisX().addCustomTick().setAllocatesAxisSpace(false)
const ticksY = series.map((el, i) => {
return chart
.getDefaultAxisY()
.addCustomTick()
.setAllocatesAxisSpace(false)
.setMarker((marker) => marker.setTextFillStyle(series[i].getStrokeStyle().getFillStyle()))
})
// Hide custom cursor components initially.
// resultTable.dispose()
rowsY.forEach(el=>{
el.dispose()
})
tickX.dispose()
ticksY.forEach((tick) => tick.dispose())
// Implement custom cursor logic with events.
chart.onSeriesBackgroundMouseMove((_, event) => {
const mouseLocationClient = { x: event.clientX, y: event.clientY }
// Translate mouse location to LCJS coordinate system for solving data points from series, and translating to Axes.
const mouseLocationEngine = chart.engine.clientLocation2Engine(mouseLocationClient.x, mouseLocationClient.y)
// Translate mouse location to Axis.
const mouseLocationAxis = translatePoint(mouseLocationEngine, chart.engine.scale, series[0].scale)
// Solve nearest data point to the mouse on each series.
const nearestDataPoints = series.map((el) => el.solveNearestFromScreen(mouseLocationEngine))
// Find the nearest solved data point to the mouse.
const nearestPoint = nearestDataPoints.reduce((prev, curr, i) => {
if (!prev) return curr
if (!curr) return prev
return Math.abs(mouseLocationAxis.y - curr.location.y) < Math.abs(mouseLocationAxis.y - prev.location.y) ? curr : prev
})
if (nearestPoint) {
// Set custom cursor location.
resultTable.setPosition({
x: nearestPoint.location.x,
y: nearestPoint.location.y,
})
// Change origin of result table based on cursor location.
if (nearestPoint.location.x > chart.getDefaultAxisX().getInterval().end / 1.5) {
if (nearestPoint.location.y > chart.getDefaultAxisY().getInterval().end / 1.5) {
resultTable.setOrigin(UIOrigins.RightTop)
} else {
resultTable.setOrigin(UIOrigins.RightBottom)
}
} else if (nearestPoint.location.y > chart.getDefaultAxisY().getInterval().end / 1.5) {
resultTable.setOrigin(UIOrigins.LeftTop)
} else {
resultTable.setOrigin(UIOrigins.LeftBottom)
}
// Format result table text.
rowX.setText(`X: ${chart.getDefaultAxisX().formatValue(nearestPoint.location.x)}`)
rowsY.forEach((rowY, i) => {
rowY.setText(`Y${i}: ${chart.getDefaultAxisY().formatValue(nearestDataPoints[i]?.location.y || 0)}`)
})
// Position custom ticks.
tickX.setValue(nearestPoint.location.x)
ticksY.forEach((tick, i) => {
tick.setValue(nearestDataPoints[i]?.location.y || 0)
})
// Display cursor.
rowX.restore()
series.forEach((el, i)=>{
if(!el.isDisposed()){
rowsY[i].restore()
}
})
tickX.restore()
ticksY.map((el) => el.restore())
} else {
// Hide cursor.
disposeCustomCursor()
tickX.dispose()
ticksY.map((el) => el.dispose())
}
})
chart.onSeriesBackgroundMouseLeave((_, e) => {
disposeCustomCursor()
tickX.dispose()
ticksY.map((el) => el.dispose())
})
chart.onSeriesBackgroundMouseDragStart((_, e) => {
disposeCustomCursor()
tickX.dispose()
ticksY.map((el) => el.dispose())
})
function disposeCustomCursor() {
rowX.dispose()
rowsY.forEach(el=>{
el.dispose()
})
}
setTimeout(() => {
series[0].dispose()
rowsY[0].dispose()
}, 3000);
setTimeout(() => {
series[0].restore()
rowsY[0].restore()
}, 6000);

Related

Handles (Edge source & target) aren't created automatically with Dagre and React-Flow. (Tree visualization)

I am creating a component tree visualisation with Dagre and React-flow, and unfortunately I face some difficulties. The edges are correct, all have the right identifiers for the source and the target, but if I don't use the Handle component provided by react-flow-renderer, the handles (small dots, connecting points for edges) won't appear. Even when I set the element targetPosition and sourcePosition. I think el.targetPosition and el.sourcePosition don't do anything. Most of the implementation below is from React-flow official website, and they don't use Handle components. Handle id is null.
You can also find a snapshot below.
Rendering the elements
{elements && (
<ReactFlowProvider>
<ReactFlow
elements={elems}
nodeTypes={{ reactComponent: ComponentNode }}
onNodeMouseEnter={(_e, node) => highlightComponent(node, false)}
onNodeMouseLeave={(_e, node) => removeHighlight(node)}
onPaneClick={resetHighlight}
>
</ReactFlow>
</ReactFlowProvider>
Rest of the code
const positionElements = (elements, dagreGraph, direction) => {
return elements.forEach((el) => {
if (isNode(el)) {
if (direction === GraphLabels.topToBottom) {
dagreGraph.setNode(el.id, {
width: nodeWidth,
height: baseNodeHeight + el.data.linesOfCode,
});
}
} else {
dagreGraph.setEdge(el.source, el.target);
}
});
};
export const getLayoutedElements = (elements, direction) => {
const dagreGraph = new dagre.graphlib.Graph(); // building the graph
dagreGraph.setDefaultEdgeLabel(() => ({}));
dagreGraph.setGraph({ rankdir: direction });
positionElements(elements, dagreGraph, direction);
dagre.layout(dagreGraph);
return elements.map((el) => {
if (isNode(el)) {
const nodeWithPosition = dagreGraph.node(el.id);
Vertical scaling
if (direction == GraphLabels.leftToRight) {
do this.
}
if (direction == GraphLabels.topToBottom) {
el.targetPosition = 'bottom';
el.sourcePosition = 'top';
el.position = {
x: someValue,
y: someOtherValue,
};
}
}
return el;
});
};

Scale of chart generated by using dc.js is returning NaN

I am very new to DC/D3 libraries. I am trying to incorporate DC with ReactJS by having a separate pure JS file that is a reusable D3 component. I am following this example here. Here is the dummy data I am using:
json snippet.
This is my App.js:
state = {
data: null,
};
componentDidMount() {
console.log("componentDidMount");
d3.json("dummy_data.json").then(data => {
this.setState({data: data});
console.log("Data is updated!");
})
this.createVisualization();
}
componentDidUpdate() {
console.log("componentDidUpdate");
this.updateVisualization();
}
createVisualization = () => {
console.log("createVisualization");
this.visualization = new Dashboard({
parentElement: d3.select(this.node)
});
}
updateVisualization = () => {
this.visualization.updateVis(this.state.data);
console.log("updateVisualization finished");
}
render() {
return (
<div style={{width: '100vw'}} id="app"
ref={node => this.node = node}>
<h1>Hello World!!!</h1>
</div>
)
}
And this is my JS file (dashboard.js) used to generate/render chart:
export default class Dashboard {
constructor(_config) {
// setting up based on input config
this.parentElement = _config.parentElement;
this.initVis();
}
initVis() {
let vis = this;
vis.chartContainer = vis
.parentElement
.append("div")
.attr("id", "chart-container")
.style("width", "100%");
vis.chart = new dc.ScatterPlot("#chart-container");
vis
.chart
.width(768)
.height(480)
.margins({top: 0, right: 10, bottom: 20, left: 50})
.x(d3.scaleLinear())
.elasticX(true)
.elasticY(true)
.brushOn(false) // turn off brush-based range filter
.symbolSize(8) // set dot radius
.clipPadding(10);
const f = vis.chart.x();
console.log(f.domain());
console.log("Finish initVis")
}
updateVis(newData) {
let vis = this;
vis.cf = crossfilter(newData);
console.log(newData);
vis.chart
.on('pretransition', () => {
// console.log(vis)
// const xext = d3.extent(vis.chart.group().all(), d => d.key);
// console.log(newData.map(d => [+d.age, +d.readmission_risk]));
const r = regression.linear(newData.map(d => [d.age, d.readmission_risk]));
const m = r.equation[0],
b = r.equation[1];
const [x1, x2] = vis.chart.x().domain();
const points = [
[
x1, m * x1 + b
],
[
x2, m * x2 + b
]
];
const xScale = vis.chart.x();
const yScale = vis.chart.y();
const margins = vis.chart.margins();
console.log(xScale(20));
var line = vis.chart.g().selectAll('line.regression').data([points]);
// console.log(vis.chart.x().domain());
function do_points(line) {
line
.attr('x1', d => xScale(d[0][0]) + margins.left)
.attr('y1', d => yScale(d[0][1]) + margins.top)
.attr('x2', d => xScale(d[1][0]) + margins.left)
.attr('y2', d => yScale(d[1][1]) + margins.top);
}
line = line.enter().append('line')
.attr('class', 'regression')
.call(do_points)
.merge(line);
line.transition().duration(vis.chart.transitionDuration()).call(do_points);
})
vis.renderVis();
}
renderVis() {
let vis = this;
const ageDimension = vis.cf.dimension(d => d.age);
const ageGroup = ageDimension.group();
vis.chart.dimension(ageDimension).group(ageGroup);
vis.chart.render();
console.log("rendering");
}
I have identified the problem to be the xScale in the call-back function in the updateVis method. It is returning NaN. But I tried to call the xScale in the initVis the scale function seems working fine and return [0,1]. I have been struggling because of this for a long time. Any help would be appreciated!
Update:
Here is a snapshot of the error message I got:
Thanks for including a reproducible example. It's really hard to debug D3 and dc.js code by just staring at it without running it.
The problem here is that the scatter plot expects the group keys to be a two-element array of numbers, not just a single number. You can see that the key_function(), in the regression example which you started from, returns a function returning such keys.
Changing your dimension accordingly:
const ageDimension = vis.cf.dimension(d => [d.age, d.readmission_risk] );
caused the chart to appear.
I also had to add the CSS from the example in order to get the regression line to show; I created dashboard.css:
line.regression {
stroke: red;
stroke-width: 5;
opacity: 0.5;
}
And added it to dashboard.js:
import './dashboard.css';
Result:
Incidentally, the way you have calculated the regression line, it won't update when the data is filtered. If you want it to do so, this should work:
const r = regression.linear(vis.chart.group().all().filter(kv => kv.value).map(kv => [kv.key[0], kv.key[1]]));
This uses the keys from the chart's group, but only the ones which are filtered "in" (value > 0).

How to flyTo and then getBounds around location based on selected location in react-leaflet

How can i zoom on particular location or flyTo that particular location dynamically when location selected dynamically from list of cities dropdown and the getBounds around that location. I am trying to reference this answer but this can work after flyTo happens. So how can use flyTo with my code.
useEffect(() => {
const cityFilter = () => {
let geocodes = JSON.parse(sessionStorage.getItem('geocode'));
let mapData = props.allEquipData.filter((filtered) => {
return filtered.city === selectedCity;
});
Promise.all(
mapData.map(async (data) => {
let selectedLocGeo = geocodes.filter((geo) => geo.zip == data.zipcode);
if (selectedLocGeo.length > 0) {
data.coordinates = [selectedLocGeo[0].latitude, selectedLocGeo[0].longitude];
} else {
data.coordinates = [0, 0];
}
return data;
})
)
.then((response) => {
let markerData = [];
let count = 1;
response.map((resp, index) => {
if (resp.coordinates[0] !== 0 && resp.coordinates[1] !== 0) {
let existingPositionIndex = markerData.findIndex(
(mark) =>
mark.positions && mark.positions.length && mark.positions[0] === resp.coordinates[0] && mark.positions[1] === resp.coordinates[1]
);
if (existingPositionIndex > -1) {
markerData[existingPositionIndex].devices.push(resp);
} else {
let obj = {
positions: resp.coordinates,
key: 'marker' + count,
devices: [resp],
location: resp.city + ', ' + resp.state,
};
markerData.push(obj);
count++;
}
}
});
// let group = new L.featureGroup([markerData[0].positions]);
// mapRef.firBounds(group.getBounds());
// console.log('oth position : ', markerData[0].positions);
// const map = mapRef.current;
// // console.log('map : ',map.leafletElement.flyTo);
// // var group = new L.featureGroup([L.marker(markerData[0].positions)]);
// // if (map) map.leafletElement.fitBounds(group.getBounds());
// if (map) {
// map.flyTo(markerData[0].positions, 10);
setFilteredMarkers(markerData);
})
.catch((err) => {
console.log('Err', err);
});
};
cityFilter();
}, [selectedCity]);
<Map
center={position}
zoom={5}
maxZoom={100}>
<LayersControl position='topright'>
<LayersControl.Overlay name='Markers' checked>
<LayerGroup>
<MyMarkersList markers={handleMarkers(markerData)} />
</LayerGroup>
</LayersControl.Overlay>
</LayersControl>
</Map>
when city will be selected from dropdown, the useEffect will be called and then it will get you the markers position based on selected city e.g [lat,log]. from this point it should flyTo that location and getBounds around it if possible.
Add these two lines of code before setFilteredMarkers(markerData);
const {latitude, longitude} = geoCodes.find(g => g.city === selectedCity)
map.flyTo([ longitude,latitude], 12);
You want to find the coordinates of the selected city which is inside geoCodes Object[]. You use array.find using the value of th eselectedCity to achieve that. This will be triggered every time the value in the dropdown changes.
Updated Demo

Get current visible chart data after scroll

I an using HighCharts HighStock in react-native app and I am trying to get the current visible data of the chart to refresh tooltip with the last visible data, I am already able to have the last value on load since it is the last value of data array but I am failing to get the value after scrolling.
events: {
render() {
const chart = this;
points = [];
Highcharts.each(chart.series, (s) => {
if (s.visible) {
const lastPoint = s.points[s.points.length - 1];
if (lastPoint.y === null) lastPoint.y = 0;
points.push(lastPoint);
}
});
chart.tooltip.refresh(points);
},
You can use Highcharts.find method and check isInside flag in points. If you want to avoid points from a navigator, check isInternal flag in series. You should also reset the state of previously hovered points:
chart: {
events: {
render() {
const chart = this,
lastPoints = chart.lastPoints,
points = [];
if (lastPoints && lastPoints.length) {
lastPoints.forEach(function(lastPoint) {
lastPoint.setState('');
});
}
Highcharts.each(chart.series, (s) => {
if (s.visible && !s.options.isInternal) {
points.push(Highcharts.find(
s.points.slice().reverse(),
function(p) {
return p.isInside ? true : false;
}));
}
});
chart.lastPoints = points;
chart.tooltip.refresh(points);
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/xwgs13r6/
API Reference:
https://api.highcharts.com/class-reference/Highcharts.Point#setState
https://api.highcharts.com/class-reference/Highcharts#.find

how do I get xstream to combine mouse events?

I'm experimenting with drag-and-drop using cyclejs in a codepen. The standard drag methods supported by HTML 5 don't seem to support constraints on the movement of the dragged object so I went with standard mousedown/mousemove/mouseup. It works, but not consistently. The combine() operation doesn't seem to trigger even when the debug() calls show that mousedown and mousemove events have been received and sometimes the mouseup is missed. Perhaps my understanding of the operation is incomplete or incorrect. A direct link to the codepen is provided at the bottom of this post. Any help appreciated!
const xs = xstream.default;
const { run } = Cycle;
const { div, svg, makeDOMDriver } = CycleDOM;
function DragBox(sources) {
const COMPONENT_NAME = `DragBox`;
const intent = function({ DOM }) {
return {
mousedown$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousedown")
.map(function(ev) {
return ev;
})
.debug("mousedown"),
mousemove$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousemove")
.map(function(ev) {
return ev;
})
.debug("mousemove"),
mouseup$: DOM.select("#container")
.events("mouseup")
.map(function(ev) {
return ev;
})
.debug("mouseup")
};
};
const between = (first, second) => {
return source => first.mapTo(source.endWhen(second)).flatten();
};
const model = function({ mousedown$, mousemove$, mouseup$ }) {
return xs
.combine(mousedown$, mousemove$)
.debug("combine")
.map(([mousedown, mousemove]) => ({
x: mousemove.pageX - mousedown.layerX,
y: mousemove.pageY - mousedown.layerY
}))
.compose(between(mousedown$, mouseup$))
.startWith({
x: 0,
y: 0
})
.debug("model");
};
const getStyle = left => top => {
return {
style: {
position: "absolute",
left: left + "px",
top: top + "px",
backgroundColor: "#333",
cursor: "move"
}
};
};
const view = function(state$) {
return state$.map(value =>
div("#container", { style: { height: "100vh" } }, [
div(`#${COMPONENT_NAME}`, getStyle(value.x)(value.y), "Move Me!")
])
);
};
const actions = intent(sources);
const state$ = model(actions);
const vTree$ = view(state$);
return {
DOM: vTree$
};
}
function main(sources) {
const dragBox = DragBox(sources);
const sinks = {
DOM: dragBox.DOM
};
return sinks;
}
Cycle.run(main, {
DOM: makeDOMDriver("#app")
});
https://codepen.io/velociflapter/pen/bvqMGp?editors=1111
Testing your code shows that combine is not getting the first mousedown event, apparently due to the between operator subscribing to mousedown$ after the first mousedown event. Adding remember to the mousedown$ sends that first mousedown event to the between operator subscription.
mousedown$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousedown").remember()
Codepen.io remember example.
Codesandbox.io testing between
Here's another CycleJS/xstream Drag and Drop approach (taking inspiration from this RxJS Drag and Drop example) I think is more straight forward. Everything else in your code is essentially the same but the model function is this:
const model = function({ mousedown$, mousemove$, mouseup$ }) {
return mousedown$.map(md => {
let startX = md.offsetX, startY = md.offsetY
return mousemove$.map(mm => {
mm.preventDefault()
return {
x: mm.clientX - startX,
y: mm.clientY - startY
}
}).endWhen(mouseup$)
}).flatten().startWith({x:0,y:0})
};
Here's a Codepen.io example.

Categories