Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems with integrating d3fc charts as a webcomponent in React application (Shadow DOM) #1806

Open
akarshnayak opened this issue Mar 6, 2024 · 0 comments

Comments

@akarshnayak
Copy link

akarshnayak commented Mar 6, 2024

So i created a custom chart using d3fc. Now i want to integrate this chart in my react application, i took the approach with integrating it by creating a web component of my chart using shadow dom.

<div id="outer-container">
    <div id="chart-container">
        </div>
    </div>

This is the basic html structure of the web component, so the outer-container and the chart-container are correctly occupying the specified dimensions. but the d3fc group constructed by the cartesian chart component is not taking the complete dimensions.

also during the setup of chart i logged the dimensions before setting up the x and y scales they correctly display the specified height.
Additionally, for some reason the scales are not being setup correctly

Also another problem is :

defineChart(xDomain,yDomain) {

    return fc.chartCartesian(this.scales.main.x, this.scales.main.y)
      .xDomain(xDomain) 
      .yDomain(yDomain) //[42730, 42755]
      .yOrient('left')
      .xOrient('bottom')
      .canvasPlotArea(this.canvasMulti)
  as you can see in my cartesian chart setup i have specified for y axis to be aligned to left and x axis to be bottom but they both seem no not align accordingly , but rather they align at the bottom of the d3fc group element. 
  
  How do i fix this problem, is it because of using it inside the shadow dom or something. 
  
  also additionally the canvas plot area height is also 0. 
  initChart() {
    console.log('foot2',this._visibleData)
      // Chart initialization logic here
      this.container = this.shadowRoot.querySelector('#chart-container');
      this.blockContainer = this.shadowRoot.querySelector('#blocks-container');
      this.blockContainer.style.top = `${this.container.offsetHeight - 35}px`;

      this.chartDimensions = {
        main: {
          width: this.container.clientWidth,
          height: this.container.clientHeight
        },
        blocks: {
          height: this.blockContainer.clientHeight
        }
      }
      console.log(this.chartDimensions)

      this.config = {
        chart: {
          margin: { top: 20, right: 20, bottom: 30, left: 50 },
          width: this.chartDimensions.main.width,
        },
        colors: {
          positiveScale: d3.scaleSequential(t => d3.interpolateBlues(t * 0.3 + 0.8)),
          negativeScale: d3.scaleSequential(t => d3.interpolateReds(t * 0.3 + 0.8)),
          defaultPositive: '#001EFF',
          defaultNegative: '#FE0000',
          largeTrade: 'lime',
        },
        blockChart: {
          keys: ['false_count', 'true_count', 'average_price', 'trade_count', 'total_volume', 'net_volumes'],
        },
      };

      this.constants = {
        block : {
          keys : ['false_count', 'true_count', 'average_price', 'trade_count', 'total_volume', 'net_volume']
        }
      }

      this.scales = {
        main : {
          x : d3.scaleTime().range([0, this.chartDimensions.main.width]),
          y : d3.scaleLinear().range([this.chartDimensions.main.height, 0]),
          xDomain : null,
        },
        blocks : {
          y: d3.scaleLinear().domain([0, 6]).range([this.chartDimensions.blocks.height, 0])
        }
      }
      this.scales.blocks.x = this.scales.main.x.copy()

      this.states = {
        timeFrame : 900,
        showDetails : false,
        showColors : false,
        showBlockDetails : false,
        plBarHeight : 0,
        level : 1,
        currDataMap:{
          startMap : null,
          endMap : null,
        },
        currCandle : {
          currColor : this.config.colors.defaultPositive,
          delta: {
            maxPositive : 0,
            maxNegative : 0
          }
        },
        indices: {
          prevClose : 0,
          barIndexCounter : 0,
          prevBarIndex : 0,
          blockIndexCounter : 0,
        }
      }

      this.data = {
        visibleData : this._visibleData,
        timestampArr : null,
        mainData : null,
        currentMainData : {
          series: null,
          crosshair : []
        },
        currentBlockData : null,
      }

      this.getBarAndBlockData(this.data.visibleData);
      this.data.timestampArr = Array.from(this.mapData.keys())

      let last15Candles = this.data.timestampArr.slice(-15)
      this.scales.main.xDomain = [last15Candles[0],last15Candles[last15Candles.length - 1]]

      const barSeries = this.defineBarSeries();
      this.blockSeries = this.defineBlockSeries();

      this.zoom = fc.zoom().on('zoom',() => this.zoomed())

      this.crosshair = this.defineCrosshair();
      const crosshair = this.crosshair
      const gridlines = this.defineGridlines();

      this.canvasMulti = fc.seriesCanvasMulti()
      .series([gridlines, barSeries ,crosshair ])
      .mapping((data, index, series) => {
      switch(series[index]) {
        case gridlines:
          return data.series;
        case barSeries:
          return data.series;
        case crosshair:
          return data.crosshair;
      }
      })

      let lastPriceLevel = parseInt(this.barData.series[this.barData.series.length - 1].priceLevel)
      this.chart = this.defineChart(this.scales.main.xDomain,[lastPriceLevel - 80, lastPriceLevel + 4])
      this.blockChart = this.defineBlockChart(this.scales.main.xDomain)

      this.render(this.barData, this.blockData)

      
      // Then access the canvas and apply high DPI settings
      const canvas = this.container.querySelector('canvas');
      if (canvas) {
        const plotArea = this.container.querySelector('.plot-area')
        plotArea.addEventListener('mouseleave', this.deactivateCrosshair)
          canvas.style.cursor = 'crosshair';

          const context = canvas.getContext('2d');

          this.crosshair.context(context)
          // Get the device pixel ratio
          const pixelRatio = window.devicePixelRatio || 1;

          // Get the size of the container element
          const containerWidth = this.container.clientWidth;
          const containerHeight = this.container.clientHeight;

          const blockCanvas = this.blockContainer.querySelector('canvas')
          const blockContext = blockCanvas.getContext('2d')

          //blockCanvas.height = blockContainer.clientHeight * pixelRatio
          //blockCanvas.width = blockContainer.clientWidth * pixelRatio

          blockContext.antialias = true
          //canvas.style.width = 1920;
          //canvas.style.height = 1000;
          
          // Later in your code where you set the canvas size
          canvas.width = containerWidth * pixelRatio;
          canvas.height = containerHeight * pixelRatio;

          ///context.antialias = true;

          // Re-render the chart to apply the high DPI settings
          this.render(this.barData , this.blockData);
      }
  }'

This is the init chart func is called when a data property of the class has been changed

import {React, useState, useContext, useEffect, useRef} from 'react';
import '../web_component/footprint-chart.es.js';
import { CoinContext } from '../contexts/CoinContext';
import axios from 'axios';

const DaftChart = () => {
  const { currentCoin } = useContext(CoinContext);
  const [data, setData] = useState(null);
  const [isLoading , setIsLoading ] = useState(false);
  const chartRef = useRef(null);

  const fetchDaftData = async () => {
      const interval = '1sec';
      const url = 'http://localhost:8080/footprint_data/'+currentCoin+'/'+interval ;
      try {
          const response = await axios.get(url);
          if(response.data) {
              console.log('foot',response.data)
              setData(response.data);
              setIsLoading(false);
          }
      } catch(error) {
          console.log('foot Error',error)
      }
  }

  useEffect(() => {
      fetchDaftData();
  },[currentCoin]);

  useEffect(() => {
      if (data && chartRef.current) {
        // Directly setting the property on the web component
        chartRef.current.visibleData = data;
      }
    }, [data]);

return (
  <div className='h-full w-full m-2' >
      {isLoading? <p>Loading...</p> : <footprint-chart ref={chartRef}></footprint-chart>}
  </div>
)
}

export default DaftChart

This is the react component that uses the web component.
Please help me fix this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants