diff --git a/src/components/d3/D3HorizontalChart.tsx b/src/components/d3/D3HorizontalChart.tsx
new file mode 100644
index 0000000..421108b
--- /dev/null
+++ b/src/components/d3/D3HorizontalChart.tsx
@@ -0,0 +1,149 @@
+import React, { memo, useEffect } from 'react'
+import * as d3 from 'd3'
+
+import LoaderPortal from '@components/LoaderPortal'
+import { useElementSize } from 'usehooks-ts'
+
+
+import '@styles/d3.less'
+
+type DataType = {
+ name: string
+ percent: number
+}
+
+type D3HorizontalChartProps = {
+ width?: string
+ height?: string
+ data: DataType[]
+ colors?: string[]
+}
+
+const D3HorizontalChart = memo((
+ {
+ width: givenWidth = '100%',
+ height: givenHeight = '100%',
+ data,
+ colors
+ }: D3HorizontalChartProps) => {
+
+ const [rootRef, { width, height }] = useElementSize()
+
+ const margin = { top: 50, right: 100, bottom: 50, left: 100 }
+
+ useEffect(() => {
+ if (width < 100 || height < 100) return
+ const _width = width - margin.left - margin.right
+ const _height = height - margin.top - margin.bottom
+
+ const svg = d3.select('#d3-horizontal-chart')
+ .attr('width', '100%')
+ .attr('height', '100%')
+ .append('g')
+ .attr('transform', `translate(${margin.left},${margin.top})`)
+
+ const percents = ['percents']
+ const names = data.map(d => d.name)
+
+ const stackedData = d3.stack()
+ //@ts-ignore
+ .keys(percents)(data)
+
+ const xMax = 100
+
+ // scales
+
+ const x = d3.scaleLinear()
+ .domain([0, xMax])
+ .range([0, _width])
+
+ const y = d3.scaleBand()
+ .domain(names)
+ .range([0, _height])
+ .padding(0.25)
+
+ // axes
+
+ const xAxisTop = d3.axisTop(x)
+ .tickValues([0, 25, 50, 75, 100])
+ .tickFormat(d => d + '%')
+
+ const xAxisBottom = d3.axisBottom(x)
+ .tickValues([0, 25, 50, 75, 100])
+ .tickFormat(d => d + '%')
+
+ const yAxisLeft = d3.axisLeft(y)
+
+ const gridlines = d3.axisBottom(x)
+ .tickValues([0, 25, 50, 75, 100])
+ .tickFormat(d => '')
+ .tickSize(_height)
+
+ const yAxisRight = d3.axisRight(y)
+ .ticks(0)
+ .tickValues([])
+ .tickFormat(d => '')
+
+ svg.append('g')
+ .attr('transform', `translate(0,0)`)
+ .attr("class", "grid-line")
+ .call(g => g.select('.domain').remove())
+ .call(gridlines)
+
+ svg.append('g')
+ .attr('transform', `translate(0,0)`)
+ .call(xAxisTop)
+
+ svg.append("g")
+ .call(yAxisLeft)
+
+ svg.append('g')
+ .attr('transform', `translate(0,${_height})`)
+ .call(xAxisBottom)
+
+ svg.append('g')
+ .attr('transform', `translate(${_width},0)`)
+ .call(yAxisRight)
+
+ const layers = svg.append('g')
+ .selectAll('g')
+ .data(stackedData)
+ .join('g')
+
+ // transition for bars
+ const duration = 1000
+ const t = d3.transition()
+ .duration(duration)
+ .ease(d3.easeLinear)
+
+ layers.each(function() {
+ d3.select(this)
+ .selectAll('rect')
+ //@ts-ignore
+ .data(d => d)
+ .join('rect')
+ .attr('fill', (d, i) => colors ? colors[i] : 'black')
+ //@ts-ignore
+ .attr('y', d => y(d.data.name))
+ .attr('height', y.bandwidth())
+ //@ts-ignore
+ .transition(t)
+ //@ts-ignore
+ .attr('width', d => x(d.data.percent))
+ })
+
+ return () => {
+ svg.selectAll("g").selectAll("*").remove()
+ }
+ }, [width, height, data])
+
+ return (
+