diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2023-05-26 08:46:24 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-05-26 20:03:09 +0000 |
commit | 53c9f82d5ed08178ef731d08e6a650878dfbbfeb (patch) | |
tree | a10050b3625d215e25b3b273ddf0dc0fcb8a8ec0 /server/sonar-web/src/main/js | |
parent | 01570b82ed0d332f930a94953d23579882bd522a (diff) | |
download | sonarqube-53c9f82d5ed08178ef731d08e6a650878dfbbfeb.tar.gz sonarqube-53c9f82d5ed08178ef731d08e6a650878dfbbfeb.zip |
SONAR-19391 Use the new Histogram component
Diffstat (limited to 'server/sonar-web/src/main/js')
7 files changed, 181 insertions, 815 deletions
diff --git a/server/sonar-web/src/main/js/components/charts/Histogram.css b/server/sonar-web/src/main/js/components/charts/Histogram.css deleted file mode 100644 index 639646d7db3..00000000000 --- a/server/sonar-web/src/main/js/components/charts/Histogram.css +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.histogram-tick { - text-anchor: end !important; -} - -.histogram-tick-start { - text-anchor: start !important; -} - -.histogram-value { - text-anchor: start !important; -} diff --git a/server/sonar-web/src/main/js/components/charts/Histogram.tsx b/server/sonar-web/src/main/js/components/charts/Histogram.tsx deleted file mode 100644 index 8794270fa0b..00000000000 --- a/server/sonar-web/src/main/js/components/charts/Histogram.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { max } from 'd3-array'; -import { scaleBand, ScaleBand, scaleLinear, ScaleLinear } from 'd3-scale'; -import * as React from 'react'; -import Tooltip from '../controls/Tooltip'; -import './BarChart.css'; -import './Histogram.css'; - -interface Props { - alignTicks?: boolean; - bars: number[]; - height: number; - padding?: [number, number, number, number]; - yTicks?: string[]; - yTooltips?: string[]; - yValues?: string[]; - width: number; -} - -const BAR_HEIGHT = 10; -const DEFAULT_PADDING = [10, 10, 10, 10]; - -type XScale = ScaleLinear<number, number>; -type YScale = ScaleBand<number>; - -export default class Histogram extends React.PureComponent<Props> { - renderBar(d: number, index: number, xScale: XScale, yScale: YScale) { - const { alignTicks, padding = DEFAULT_PADDING } = this.props; - - const width = Math.round(xScale(d)) + /* minimum bar width */ 1; - const x = xScale.range()[0] + (alignTicks ? padding[3] : 0); - const y = Math.round((yScale(index) || 0) + yScale.bandwidth() / 2); - - return <rect className="bar-chart-bar" height={BAR_HEIGHT} width={width} x={x} y={y} />; - } - - renderValue(d: number, index: number, xScale: XScale, yScale: YScale) { - const { alignTicks, padding = DEFAULT_PADDING, yValues } = this.props; - - const value = yValues && yValues[index]; - - if (!value) { - return null; - } - - const x = xScale(d) + (alignTicks ? padding[3] : 0); - const y = Math.round((yScale(index) || 0) + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); - - return ( - <Tooltip overlay={this.props.yTooltips && this.props.yTooltips[index]}> - <text className="bar-chart-tick histogram-value" dx="1em" dy="0.3em" x={x} y={y}> - {value} - </text> - </Tooltip> - ); - } - - renderTick(index: number, xScale: XScale, yScale: YScale) { - const { alignTicks, yTicks } = this.props; - - const tick = yTicks && yTicks[index]; - - if (!tick) { - return null; - } - - const x = xScale.range()[0]; - const y = Math.round((yScale(index) || 0) + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); - const historyTickClass = alignTicks ? 'histogram-tick-start' : 'histogram-tick'; - - return ( - <text - className={'bar-chart-tick ' + historyTickClass} - dx={alignTicks ? 0 : '-1em'} - dy="0.3em" - x={x} - y={y} - > - {tick} - </text> - ); - } - - renderBars(xScale: XScale, yScale: YScale) { - return ( - <g> - {this.props.bars.map((d, index) => { - return ( - // eslint-disable-next-line react/no-array-index-key - <g key={index}> - {this.renderBar(d, index, xScale, yScale)} - {this.renderValue(d, index, xScale, yScale)} - {this.renderTick(index, xScale, yScale)} - </g> - ); - })} - </g> - ); - } - - render() { - const { bars, width, height, padding = DEFAULT_PADDING } = this.props; - - const availableWidth = width - padding[1] - padding[3]; - const xScale: XScale = scaleLinear() - .domain([0, max(bars)!]) - .range([0, availableWidth]); - - const availableHeight = height - padding[0] - padding[2]; - const yScale: YScale = scaleBand<number>() - .domain(bars.map((_, index) => index)) - .rangeRound([0, availableHeight]); - - return ( - <svg className="bar-chart" height={this.props.height} width={this.props.width}> - <g transform={`translate(${this.props.alignTicks ? 4 : padding[3]}, ${padding[0]})`}> - {this.renderBars(xScale, yScale)} - </g> - </svg> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx index f1038db36b0..3d77f2149c5 100644 --- a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx +++ b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx @@ -17,16 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Histogram } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import withLanguagesContext from '../../app/components/languages/withLanguagesContext'; -import Histogram from '../../components/charts/Histogram'; import { translate } from '../../helpers/l10n'; import { formatMeasure } from '../../helpers/measures'; import { Languages } from '../../types/languages'; import { MetricType } from '../../types/metrics'; -interface LanguageDistributionProps { +export interface LanguageDistributionProps { distribution: string; languages: Languages; } @@ -34,24 +34,28 @@ interface LanguageDistributionProps { const NUMBER_FORMAT_THRESHOLD = 1000; export function LanguageDistribution(props: LanguageDistributionProps) { - let distribution = props.distribution.split(';').map((point) => { + const { distribution, languages } = props; + let parsedDistribution = distribution.split(';').map((point) => { const tokens = point.split('='); return { language: tokens[0], lines: parseInt(tokens[1], 10) }; }); - distribution = sortBy(distribution, (d) => -d.lines); + parsedDistribution = sortBy(parsedDistribution, (d) => -d.lines); - const data = distribution.map((d) => d.lines); - const yTicks = distribution.map((d) => getLanguageName(d.language)).map(cutLanguageName); - const yTooltips = distribution.map((d) => + const data = parsedDistribution.map((d) => d.lines); + const yTicks = parsedDistribution + .map((d) => getLanguageName(languages, d.language)) + .map(cutLanguageName); + const yTooltips = parsedDistribution.map((d) => d.lines > NUMBER_FORMAT_THRESHOLD ? formatMeasure(d.lines, MetricType.Integer) : '' ); - const yValues = distribution.map((d) => formatMeasure(d.lines, MetricType.ShortInteger)); + const yValues = parsedDistribution.map((d) => formatMeasure(d.lines, MetricType.ShortInteger)); return ( <Histogram bars={data} - height={distribution.length * 25} + height={parsedDistribution.length * 25} + leftAlignTicks={true} padding={[0, 60, 0, 80]} width={260} yTicks={yTicks} @@ -59,14 +63,14 @@ export function LanguageDistribution(props: LanguageDistributionProps) { yValues={yValues} /> ); +} - function getLanguageName(langKey: string) { - if (langKey === '<null>') { - return translate('unknown'); - } - const lang = props.languages[langKey]; - return lang ? lang.name : langKey; +function getLanguageName(languages: Languages, langKey: string) { + if (langKey === '<null>') { + return translate('unknown'); } + const lang = languages[langKey]; + return lang ? lang.name : langKey; } function cutLanguageName(name: string) { diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx deleted file mode 100644 index a8325bb3387..00000000000 --- a/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { scaleBand } from 'd3-scale'; -import { shallow } from 'enzyme'; -import * as React from 'react'; -import Histogram from '../Histogram'; - -jest.mock('d3-scale', () => { - const d3 = jest.requireActual('d3-scale'); - return { - ...d3, - scaleBand: jest.fn(d3.scaleBand), - }; -}); - -beforeEach(jest.clearAllMocks); - -it('renders correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ alignTicks: true })).toMatchSnapshot('align ticks'); - expect(shallowRender({ yValues: ['100.0', '75.0', '150.0'] })).toMatchSnapshot('with yValues'); - expect( - shallowRender({ yTicks: ['a', 'b', 'c'], yValues: ['100.0', '75.0', '150.0'] }) - ).toMatchSnapshot('with yValues and yTicks'); - expect( - shallowRender({ - yTicks: ['a', 'b', 'c'], - yTooltips: ['a - 100', 'b - 75', 'c - 150'], - yValues: ['100.0', '75.0', '150.0'], - }) - ).toMatchSnapshot('with yValues, yTicks and yTooltips'); -}); - -it('correctly handles yScale() returning undefined', () => { - const yScale = () => undefined; - yScale.bandwidth = () => 1; - - (scaleBand as jest.Mock).mockReturnValueOnce({ - domain: () => ({ rangeRound: () => yScale }), - }); - - expect( - shallowRender({ yValues: ['100.0', '75.0', '150.0'], yTicks: ['a', 'b', 'c'] }) - ).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<Histogram['props']> = {}) { - return shallow<Histogram>(<Histogram bars={[100, 75, 150]} height={75} width={100} {...props} />); -} diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/LanguageDistribution-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/LanguageDistribution-test.tsx index ee7486a9b61..3466cbef9a8 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/LanguageDistribution-test.tsx +++ b/server/sonar-web/src/main/js/components/charts/__tests__/LanguageDistribution-test.tsx @@ -17,17 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { shallow } from 'enzyme'; + import * as React from 'react'; -import { LanguageDistribution } from '../LanguageDistribution'; +import { renderComponent } from '../../../helpers/testReactTestingUtils'; +import LanguageDistribution, { LanguageDistributionProps } from '../LanguageDistribution'; -it('renders', () => { - expect( - shallow( - <LanguageDistribution - distribution="java=1734;js=845;cpp=73;<null>=15" - languages={{ java: { key: 'java', name: 'Java' }, js: { key: 'js', name: 'JavaScript' } }} - /> - ) - ).toMatchSnapshot(); +it('should render correctly', () => { + const { container } = renderLanguageDistribution(); + expect(container).toMatchSnapshot(); }); + +function renderLanguageDistribution(props: Partial<LanguageDistributionProps> = {}) { + return renderComponent( + <LanguageDistribution + distribution="java=1734;js=845;cpp=73;<null>=15" + languages={{ java: { key: 'java', name: 'Java' }, js: { key: 'js', name: 'JavaScript' } }} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap deleted file mode 100644 index 9f2ac323b09..00000000000 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap +++ /dev/null @@ -1,508 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`correctly handles yScale() returning undefined 1`] = ` -<svg - className="bar-chart" - height={75} - width={100} -> - <g - transform="translate(10, 10)" - > - <g> - <g - key="0" - > - <rect - className="bar-chart-bar" - height={10} - width={54} - x={0} - y={1} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={53.33333333333333} - y={6} - > - 100.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={6} - > - a - </text> - </g> - <g - key="1" - > - <rect - className="bar-chart-bar" - height={10} - width={41} - x={0} - y={1} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={40} - y={6} - > - 75.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={6} - > - b - </text> - </g> - <g - key="2" - > - <rect - className="bar-chart-bar" - height={10} - width={81} - x={0} - y={1} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={80} - y={6} - > - 150.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={6} - > - c - </text> - </g> - </g> - </g> -</svg> -`; - -exports[`renders correctly: align ticks 1`] = ` -<svg - className="bar-chart" - height={75} - width={100} -> - <g - transform="translate(4, 10)" - > - <g> - <g - key="0" - > - <rect - className="bar-chart-bar" - height={10} - width={54} - x={10} - y={10} - /> - </g> - <g - key="1" - > - <rect - className="bar-chart-bar" - height={10} - width={41} - x={10} - y={28} - /> - </g> - <g - key="2" - > - <rect - className="bar-chart-bar" - height={10} - width={81} - x={10} - y={46} - /> - </g> - </g> - </g> -</svg> -`; - -exports[`renders correctly: default 1`] = ` -<svg - className="bar-chart" - height={75} - width={100} -> - <g - transform="translate(10, 10)" - > - <g> - <g - key="0" - > - <rect - className="bar-chart-bar" - height={10} - width={54} - x={0} - y={10} - /> - </g> - <g - key="1" - > - <rect - className="bar-chart-bar" - height={10} - width={41} - x={0} - y={28} - /> - </g> - <g - key="2" - > - <rect - className="bar-chart-bar" - height={10} - width={81} - x={0} - y={46} - /> - </g> - </g> - </g> -</svg> -`; - -exports[`renders correctly: with yValues 1`] = ` -<svg - className="bar-chart" - height={75} - width={100} -> - <g - transform="translate(10, 10)" - > - <g> - <g - key="0" - > - <rect - className="bar-chart-bar" - height={10} - width={54} - x={0} - y={10} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={53.33333333333333} - y={15} - > - 100.0 - </text> - </Tooltip> - </g> - <g - key="1" - > - <rect - className="bar-chart-bar" - height={10} - width={41} - x={0} - y={28} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={40} - y={33} - > - 75.0 - </text> - </Tooltip> - </g> - <g - key="2" - > - <rect - className="bar-chart-bar" - height={10} - width={81} - x={0} - y={46} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={80} - y={51} - > - 150.0 - </text> - </Tooltip> - </g> - </g> - </g> -</svg> -`; - -exports[`renders correctly: with yValues and yTicks 1`] = ` -<svg - className="bar-chart" - height={75} - width={100} -> - <g - transform="translate(10, 10)" - > - <g> - <g - key="0" - > - <rect - className="bar-chart-bar" - height={10} - width={54} - x={0} - y={10} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={53.33333333333333} - y={15} - > - 100.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={15} - > - a - </text> - </g> - <g - key="1" - > - <rect - className="bar-chart-bar" - height={10} - width={41} - x={0} - y={28} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={40} - y={33} - > - 75.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={33} - > - b - </text> - </g> - <g - key="2" - > - <rect - className="bar-chart-bar" - height={10} - width={81} - x={0} - y={46} - /> - <Tooltip> - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={80} - y={51} - > - 150.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={51} - > - c - </text> - </g> - </g> - </g> -</svg> -`; - -exports[`renders correctly: with yValues, yTicks and yTooltips 1`] = ` -<svg - className="bar-chart" - height={75} - width={100} -> - <g - transform="translate(10, 10)" - > - <g> - <g - key="0" - > - <rect - className="bar-chart-bar" - height={10} - width={54} - x={0} - y={10} - /> - <Tooltip - overlay="a - 100" - > - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={53.33333333333333} - y={15} - > - 100.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={15} - > - a - </text> - </g> - <g - key="1" - > - <rect - className="bar-chart-bar" - height={10} - width={41} - x={0} - y={28} - /> - <Tooltip - overlay="b - 75" - > - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={40} - y={33} - > - 75.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={33} - > - b - </text> - </g> - <g - key="2" - > - <rect - className="bar-chart-bar" - height={10} - width={81} - x={0} - y={46} - /> - <Tooltip - overlay="c - 150" - > - <text - className="bar-chart-tick histogram-value" - dx="1em" - dy="0.3em" - x={80} - y={51} - > - 150.0 - </text> - </Tooltip> - <text - className="bar-chart-tick histogram-tick" - dx="-1em" - dy="0.3em" - x={0} - y={51} - > - c - </text> - </g> - </g> - </g> -</svg> -`; diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/LanguageDistribution-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/LanguageDistribution-test.tsx.snap index 5f729e9ef4b..5fed7969987 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/LanguageDistribution-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/LanguageDistribution-test.tsx.snap @@ -1,48 +1,149 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders 1`] = ` -<Histogram - bars={ - [ - 1734, - 845, - 73, - 15, - ] - } - height={100} - padding={ - [ - 0, - 60, - 0, - 80, - ] - } - width={260} - yTicks={ - [ - "Java", - "JavaScript", - "cpp", - "unknown", - ] - } - yTooltips={ - [ - "1,734", - "", - "", - "", - ] - } - yValues={ - [ - "1.7short_number_suffix.k", - "845", - "73", - "15", - ] - } -/> +exports[`should render correctly 1`] = ` +.emotion-0 { + fill: rgb(93,108,208); +} + +.emotion-2 { + font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + fill: rgb(106,117,144); +} + +.e1vbniy52 .emotion-2 { + fill: rgb(255,255,255); +} + +<div> + <svg + height="100" + width="260" + > + <g + transform="translate(0, 0)" + > + <g> + <g> + <rect + class="emotion-0 emotion-1" + height="10" + width="121" + x="80" + y="13" + /> + <text + class="emotion-2 emotion-3" + dx="1em" + dy="0.3em" + text-anchor="start" + x="200" + y="18" + > + 1.7short_number_suffix.k + </text> + <text + class="emotion-2 emotion-3" + dx="0" + dy="0.3em" + text-anchor="start" + x="0" + y="18" + > + Java + </text> + </g> + <g> + <rect + class="emotion-0 emotion-1" + height="10" + width="59" + x="80" + y="38" + /> + <text + class="emotion-2 emotion-3" + dx="1em" + dy="0.3em" + text-anchor="start" + x="138.47750865051904" + y="43" + > + 845 + </text> + <text + class="emotion-2 emotion-3" + dx="0" + dy="0.3em" + text-anchor="start" + x="0" + y="43" + > + JavaScript + </text> + </g> + <g> + <rect + class="emotion-0 emotion-1" + height="10" + width="6" + x="80" + y="63" + /> + <text + class="emotion-2 emotion-3" + dx="1em" + dy="0.3em" + text-anchor="start" + x="85.05190311418686" + y="68" + > + 73 + </text> + <text + class="emotion-2 emotion-3" + dx="0" + dy="0.3em" + text-anchor="start" + x="0" + y="68" + > + cpp + </text> + </g> + <g> + <rect + class="emotion-0 emotion-1" + height="10" + width="2" + x="80" + y="88" + /> + <text + class="emotion-2 emotion-3" + dx="1em" + dy="0.3em" + text-anchor="start" + x="81.03806228373702" + y="93" + > + 15 + </text> + <text + class="emotion-2 emotion-3" + dx="0" + dy="0.3em" + text-anchor="start" + x="0" + y="93" + > + unknown + </text> + </g> + </g> + </g> + </svg> +</div> `; |