question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Update react state from touchhandler

See original GitHub issue

I am trying to indicate which bar is selected and when doing so change a react state which also updates the bars color. The issue I am facing is that it only works as soon as I add a console.log() inside the touch handler, which then somehow makes it trigger correctly. I tried multiple ways of doing it but never got it to work like intented.

image

import { useTheme } from '@emotion/react';
import {
  Canvas,
  Group,
  Paint,
  Path,
  Skia,
  useDerivedValue,
  useTouchHandler,
  useValue,
} from '@shopify/react-native-skia';
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import Axis from '../charts/barchart/Axis';
import Orientation from '../charts/barchart/Constants';
import NewLegend from '../charts/barchart/NewLegend';
import { scaleBand, scaleLinear, scaleOrdinal } from 'd3-scale';
import { max } from 'd3-arrays';
import { line as d3Line } from 'd3-shape';
import { View } from 'react-native';

const createLinePath = (data, xMax, yMax, xBarScale, yLineScale) => {
  const line = d3Line()
    .x((d) => xBarScale(d.name) + xBarScale.bandwidth() / 2)
    .y((d) => yLineScale(d.performance));

  return Skia.Path.MakeFromSVGString(line(data));
};

const insideBounds = (rect, x, y) => {
  return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
};

const HistoryChart = ({
  data,
  xAccessor,
  yAccessor,
  width = 200,
  height = 200,
  offset = 8,
  lineCount = 6,
  verticalMargin = 8,
  margin = { top: 20, right: 40, bottom: 20, left: 40 },
  selected,
  setSelected,
  y,
  children,
}) => {
  if (!data) {
    throw Error('No data set!');
  } else if (!xAccessor) {
    throw Error('No xAccessor set!');
  } else if (!yAccessor) {
    throw Error('No yAccessor set!');
  }

  const theme = useTheme();
  const yMax = height - margin.bottom - margin.top;
  const xMax = width - margin.left - margin.right;

  const xBarScale = scaleBand()
    .domain(data.map((val) => xAccessor(val)))
    .range([0, xMax])
    .paddingInner(0.2)
    .paddingOuter(0.2)
    .round(false);

  const yBarScale = scaleLinear()
    .domain([0, max(data, (d) => yAccessor(d))])
    .range([yMax, 0])
    .nice();

  const yLineScale = scaleLinear()
    // .domain(d3.extent(data, (d) => d.performance))
    .domain([0, max(data, (d) => d.performance)])
    .range([yMax, 0])
    .nice();

  const legendScale = scaleOrdinal()
    .domain(['Plant TCH (t/ha)', 'Ratoon TCH (t/ha)', 'Perfomance Ration (%)'])
    .range(['#377eb8', '#4daf4a', '#ff7f00']);

  const touchHandler = useTouchHandler(
    {
      onStart: ({ x, y }) => {
        data.forEach((d) => {
          const name = xAccessor(d);
          const barWidth = xBarScale.bandwidth();
          const barHeight = yMax - (yBarScale(yAccessor(d)) ?? 0);
          const barX = xBarScale(name);
          const barY = yMax - barHeight;
          const rect = {
            x: barX + margin.left,
            y: barY + margin.top,
            width: barWidth,
            height: barHeight,
          };
          if (insideBounds(rect, x, y)) {
            setSelected(d.seasonName);
          }
        });
      },
    },
    [setSelected, selected],
  );

  return (
    <View style={{ flexDirection: 'column', alignItems: 'center' }}>
      <NewLegend
        round
        scale={legendScale}
        style={{
          marginHorizontal: 8,
          justifyContent: 'center',
        }}
      />
      <View style={{ width, height }}>
        <Canvas style={{ flex: 1 }} onTouch={touchHandler}>
          <Group
            origin={{ x: 128, y: 128 }}
            transform={[{ translateY: margin.top }, { translateX: margin.left }]}>
            {data.map((d, index) => {
              const path = Skia.Path.Make();
              const name = xAccessor(d);
              const barWidth = xBarScale.bandwidth();
              const barHeight = yMax - (yBarScale(yAccessor(d)) ?? 0);
              const barX = xBarScale(name);
              const barY = yMax - barHeight;
              const rect = {
                x: barX,
                y: barY,
                width: barWidth,
                height: barHeight,
              };
              path.addRect(rect, false);
              path.close();
              return (
                <Path
                  key={`bar-${name}`}
                  path={path}
                  color={
                    d.seasonName === selected ? '#4ea0a3' : d.ratoon === 0 ? '#377eb8' : '#4daf4a'
                  }
                />
              );
            })}
            <Path
              path={createLinePath(data, xMax, yMax, xBarScale, yLineScale)}
              color={'#ff7f00'}
              style={'stroke'}
              strokeWidth={2}
            />
            <Axis
              scale={yBarScale}
              orientation={Orientation.left}
              strokeColor={theme.colors.text}
              textColor={theme.colors.text}
              label="(t/ha)"
            />
            <Axis
              scale={xBarScale}
              top={yMax}
              orientation={Orientation.bottom}
              strokeColor={theme.colors.text}
              textColor={theme.colors.text}
            />
            <Axis
              scale={yLineScale}
              orientation={Orientation.right}
              left={xMax}
              strokeColor={theme.colors.text}
              textColor={theme.colors.text}
              label="(%)"
            />
          </Group>
        </Canvas>
      </View>
    </View>
  );
};

HistoryChart.propTypes = {
  data: PropTypes.array,
  colors: PropTypes.array,
  width: PropTypes.number,
  height: PropTypes.number,
  offset: PropTypes.number,
  valueExtractor: PropTypes.func,
  nameExtractor: PropTypes.func,
  children: PropTypes.any,
};

export default HistoryChart;

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:9

github_iconTop GitHub Comments

1reaction
BubbleTrouble14commented, Jun 14, 2022

Yea thx. Really enjoying the library so far.

1reaction
BubbleTrouble14commented, Jun 14, 2022

Yes doing so now.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Updating Objects in State - React Docs
State can hold any kind of JavaScript value, including objects. But you shouldn't change objects that you hold in the React state directly....
Read more >
Touch handler not called with React Native - Stack Overflow
I just started learning React Native and ran into this: I've created a component ... setState(prevState => ({ age: prevState.age + 1 })) ......
Read more >
Updating State with Events - LearnHowToProgram.com
In this lesson, we'll handle our first event in a React application. We've handled many events before — every time we use functions...
Read more >
Handling touchscreen or mouse events: Simple Touch Handler
The Simple Touch Handler can be configured to react to particular touch or mouse ... Handler object manages several variables reflecting its current...
Read more >
Handling Touches - React Native
Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found