카테고리 없음

트레이딩뷰챠트(tradingview) + vue3 기본차트 띄우기

소행성왕자 2023. 1. 12. 14:22

 

vue3 를 사용하여 기본적인 트레이딩뷰챠트(lightweight-charts) 를 띄우려고 한다.

KB star 에서 사용하던 vue3 프로젝트를 기본으로 한다. 

트레이딩뷰챠트를 사용하기 위해 가장 먼저 해야 할 일은 lightweight-charts 에서 설치하는 것입니다 .

npm install --save lightweight-charts

componests 구조

폴더구조

 

vue3 컴포넌트 참고

https://stackblitz.com/edit/vitejs-vite-r4bbai?file=src%2Fmain.js 

vue ref() 참고

https://www.danvega.dev/blog/2020/02/12/vue3-ref-vs-reactive/

App.vue

<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import { ref } from 'vue';

/*
 * There are example components in both API styles: Options API, and Composition API
 *
 * Select your preferred style from the imports below:
 */
import LWChart from './components/composition-api/LWChart.vue';
// import LWChart from './components/options-api/LWChart.vue';

/**
 * Generates sample data for the lightweight chart
 * @param  {Boolean} ohlc Whether generated dat should include open, high, low, and close values
 * @returns {Array} sample data
 */
function generateSampleData(ohlc) {
  const randomFactor = 25 + Math.random() * 25;
  function samplePoint(i) {
    return (
      i *
        (0.5 +
          Math.sin(i / 10) * 0.2 +
          Math.sin(i / 20) * 0.4 +
          Math.sin(i / randomFactor) * 0.8 +
          Math.sin(i / 500) * 0.5) +
      200
    );
  }

  const res = [];
  let date = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
  const numberOfPoints = ohlc ? 100 : 500;
  for (var i = 0; i < numberOfPoints; ++i) {
    const time = date.getTime() / 1000;
    const value = samplePoint(i);
    if (ohlc) {
      const randomRanges = [
        -1 * Math.random(),
        Math.random(),
        Math.random(),
      ].map((i) => i * 10);
      const sign = Math.sin(Math.random() - 0.5);
      res.push({
        time,
        low: value + randomRanges[0],
        high: value + randomRanges[1],
        open: value + sign * randomRanges[2],
        close: samplePoint(i + 1),
      });
    } else {
      res.push({
        time,
        value,
      });
    }

    date.setUTCDate(date.getUTCDate() + 1);
  }

  return res;
}

const chartOptions = ref({});
const data = ref(generateSampleData(false));
const seriesOptions = ref({
  color: 'rgb(45, 77, 205)',
});
const chartType = ref('line');
const lwChart = ref();

function randomShade() {
  return Math.round(Math.random() * 255);
}

const randomColor = (alpha = 1) => {
  return `rgba(${randomShade()}, ${randomShade()}, ${randomShade()}, ${alpha})`;
};

const colorsTypeMap = {
  area: [
    ['topColor', 0.4],
    ['bottomColor', 0],
    ['lineColor', 1],
  ],
  bar: [
    ['upColor', 1],
    ['downColor', 1],
  ],
  baseline: [
    ['topFillColor1', 0.28],
    ['topFillColor2', 0.05],
    ['topLineColor', 1],
    ['bottomFillColor1', 0.28],
    ['bottomFillColor2', 0.05],
    ['bottomLineColor', 1],
  ],
  candlestick: [
    ['upColor', 1],
    ['downColor', 1],
    ['borderUpColor', 1],
    ['borderDownColor', 1],
    ['wickUpColor', 1],
    ['wickDownColor', 1],
  ],
  histogram: [['color', 1]],
  line: [['color', 1]],
};

// Set a random colour for the series as an example of how
// to apply new options to series. A similar appraoch will work on the
// option properties.
const changeColors = () => {
  const options = {};
  const colorsToSet = colorsTypeMap[chartType.value];
  colorsToSet.forEach((c) => {
    options[c[0]] = randomColor(c[1]);
  });
  seriesOptions.value = options;
};

const changeData = () => {
  const candlestickTypeData = ['candlestick', 'bar'].includes(chartType.value);
  const newData = generateSampleData(candlestickTypeData);
  data.value = newData;
  if (chartType.value === 'baseline') {
    const average =
      newData.reduce((s, c) => {
        return s + c.value;
      }, 0) / newData.length;
    seriesOptions.value = { baseValue: { type: 'price', price: average } };
  }
};

const changeType = () => {
  const types = [
    'line',
    'area',
    'baseline',
    'histogram',
    'candlestick',
    'bar',
  ].filter((t) => t !== chartType.value);
  const randIndex = Math.round(Math.random() * (types.length - 1));
  chartType.value = types[randIndex];
  changeData();

  // call a method on the component.
  lwChart.value.fitContent();
};
</script>

<template>
  <div class="chart-container">
    <LWChart
      :type="chartType"
      :data="data"
      :autosize="true"
      :chart-options="chartOptions"
      :series-options="seriesOptions"
      ref="lwChart"
    />
  </div>
  <button type="button" @click="changeColors">Set Random Colors</button>
  <button type="button" @click="changeType">Change Chart Type</button>
  <button type="button" @click="changeData">Change Data</button>
</template>
<style scoped>
.chart-container {
  margin: auto;
  width: 95%;
  height: 300px;
}
</style>

 

main.js

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

 

style.css

:root {
    font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
    font-size: 16px;
    line-height: 24px;
    font-weight: 400;
  
    color: #213547;
    background-color: #ffffff;
  
    font-synthesis: none;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    -webkit-text-size-adjust: 100%;
  }
  
  body {
    margin: 0;
  }
  
  button {
    border-radius: 8px;
    border: 1px solid transparent;
    padding: 0.5em 1em;
    font-size: 1em;
    font-weight: 500;
    font-family: inherit;
    background-color: #e9e9e9;
    cursor: pointer;
    transition: border-color 0.25s;
    margin-left: 0.5em;
  }
  button:hover {
    border-color: #646cff;
  }
  button:focus,
  button:focus-visible {
    outline: 4px auto -webkit-focus-ring-color;
  }
  
  #app {
    height: 100vh;
    width: 100vw;
  }

 

LWChart.vue

<script setup>
import {
  ref,
  onMounted,
  onUnmounted,
  watch,
  defineExpose,
  defineProps,
} from 'vue';
import { createChart } from 'lightweight-charts';

const props = defineProps({
  type: {
    type: String,
    default: 'line',
  },
  data: {
    type: Array,
    required: true,
  },
  autosize: {
    default: true,
    type: Boolean,
  },
  chartOptions: {
    type: Object,
  },
  seriesOptions: {
    type: Object,
  },
  timeScaleOptions: {
    type: Object,
  },
  priceScaleOptions: {
    type: Object,
  },
});

// Function to get the correct series constructor name for current series type.
function getChartSeriesConstructorName(type) {
  return `add${type.charAt(0).toUpperCase() + type.slice(1)}Series`;
}

// Lightweight Chart instances are stored as normal JS variables
// If you need to use a ref then it is recommended that you use `shallowRef` instead
let series;
let chart;

const chartContainer = ref();

const fitContent = () => {
  if (!chart) return;
  chart.timeScale().fitContent();
};

const getChart = () => {
  return chart;
};

defineExpose({ fitContent, getChart });

// Auto resizes the chart when the browser window is resized.
const resizeHandler = () => {
  if (!chart || !chartContainer.value) return;
  const dimensions = chartContainer.value.getBoundingClientRect();
  chart.resize(dimensions.width, dimensions.height);
};

// Creates the chart series and sets the data.
const addSeriesAndData = (props) => {
  const seriesConstructor = getChartSeriesConstructorName(props.type);
  series = chart[seriesConstructor](props.seriesOptions);
  series.setData(props.data);
};

onMounted(() => {
  // Create the Lightweight Charts Instance using the container ref.
  chart = createChart(chartContainer.value, props.chartOptions);
  addSeriesAndData(props);

  if (props.priceScaleOptions) {
    chart.priceScale().applyOptions(props.priceScaleOptions);
  }

  if (props.timeScaleOptions) {
    chart.timeScale().applyOptions(props.timeScaleOptions);
  }

  chart.timeScale().fitContent();

  if (props.autosize) {
    window.addEventListener('resize', resizeHandler);
  }
});

onUnmounted(() => {
  if (chart) {
    chart.remove();
    chart = null;
  }
  if (series) {
    series = null;
  }
});

/*
 * Watch for changes to any of the component properties.

 * If an options property is changed then we will apply those options
 * on top of any existing options previously set (since we are using the
 * `applyOptions` method).
 * 
 * If there is a change to the chart type, then the existing series is removed
 * and the new series is created, and assigned the data.
 * 
 */
watch(
  () => props.autosize,
  (enabled) => {
    if (!enabled) {
      window.removeEventListener('resize', resizeHandler);
      return;
    }
    window.addEventListener('resize', resizeHandler);
  }
);

watch(
  () => props.type,
  (newType) => {
    if (series && chart) {
      chart.removeSeries(series);
    }
    addSeriesAndData(props);
  }
);

watch(
  () => props.data,
  (newData) => {
    if (!series) return;
    series.setData(newData);
  }
);

watch(
  () => props.chartOptions,
  (newOptions) => {
    if (!chart) return;
    chart.applyOptions(newOptions);
  }
);

watch(
  () => props.seriesOptions,
  (newOptions) => {
    if (!series) return;
    series.applyOptions(newOptions);
  }
);

watch(
  () => props.priceScaleOptions,
  (newOptions) => {
    if (!chart) return;
    chart.priceScale().applyOptions(newOptions);
  }
);

watch(
  () => props.timeScaleOptions,
  (newOptions) => {
    if (!chart) return;
    chart.timeScale().applyOptions(newOptions);
  }
);
</script>

<template>
  <div class="lw-chart" ref="chartContainer"></div>
</template>

<style scoped>
.lw-chart {
  height: 100%;
}
</style>

 

참고 

https://tradingview.github.io/lightweight-charts/tutorials/vuejs/wrapper

 

Vue.js - Wrapper Component | Lightweight Charts

A simple example of how to use Lightweight Charts within the Vue.js framework.

tradingview.github.io