feat: add thousands separator settings

This commit is contained in:
ulleo
2026-06-24 17:45:33 +08:00
committed by ulleo
parent dc2b0788f4
commit a3b45754a3
16 changed files with 206 additions and 62 deletions
@@ -0,0 +1,6 @@
<svg t="1782123125056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5113"
width="200" height="200">
<path
d="M947.424 854.464c21.824 0 30.176 8.256 30.176 29.568s-8.352 29.568-30.176 29.568H76.576c-21.824 0-30.176-8.256-30.176-29.568s8.352-29.568 30.176-29.568h870.848zM110.144 617.696v50.304l-19.456 44.544-1.376 2.528a20 20 0 0 1-16.96 9.12h-22.528v-106.496h60.32z m189.28-317.888c27.712 0 50.912 8.704 70.368 26.304 19.104 17.28 28.384 37.568 28.384 61.76v248.32c0 24.16-9.28 44.448-28.384 61.76-19.424 17.568-42.656 26.24-70.368 26.24-27.712 0-50.912-8.672-70.368-26.24-19.104-17.312-28.384-37.632-28.384-61.824v-248.32c0-24.128 9.28-44.416 28.384-61.696 19.456-17.6 42.688-26.304 70.4-26.304z m288 0c27.712 0 50.912 8.704 70.368 26.304 19.104 17.28 28.384 37.568 28.384 61.76v248.32c0 24.16-9.28 44.448-28.384 61.76-19.424 17.568-42.656 26.24-70.368 26.24-27.712 0-50.912-8.672-70.368-26.24-19.104-17.312-28.384-37.632-28.384-61.824v-248.32c0-24.128 9.28-44.416 28.384-61.696 19.456-17.6 42.688-26.304 70.4-26.304z m288 0c27.712 0 50.912 8.704 70.368 26.304 19.104 17.28 28.384 37.568 28.384 61.76v248.32c0 24.16-9.28 44.448-28.384 61.76-19.424 17.568-42.656 26.24-70.368 26.24-27.712 0-50.912-8.672-70.368-26.24-19.104-17.312-28.384-37.632-28.384-61.824v-248.32c0-24.128 9.28-44.416 28.384-61.696 19.456-17.6 42.688-26.304 70.4-26.304z m-576 51.872c-10.144 0-19.264 3.424-26.624 10.112-7.68 6.976-11.776 15.968-11.776 26.08v248.32c0 10.144 4.16 19.136 11.84 26.048 7.296 6.656 16.416 10.08 26.56 10.08 10.176 0 19.264-3.424 26.624-10.112 7.68-6.88 11.776-15.904 11.776-26.08V387.84c0-10.112-4.096-19.104-11.776-26.08a38.656 38.656 0 0 0-26.624-10.112z m288 0c-10.144 0-19.264 3.424-26.624 10.112-7.68 6.976-11.776 15.968-11.776 26.08v248.32c0 10.144 4.16 19.136 11.84 26.048 7.296 6.656 16.416 10.08 26.56 10.08 10.176 0 19.264-3.424 26.624-10.112 7.68-6.88 11.776-15.904 11.776-26.08V387.84c0-10.112-4.096-19.104-11.776-26.08a38.656 38.656 0 0 0-26.624-10.112z m288 0c-10.144 0-19.264 3.424-26.624 10.112-7.68 6.976-11.776 15.968-11.776 26.08v248.32c0 10.144 4.16 19.136 11.84 26.048 7.296 6.656 16.416 10.08 26.56 10.08 10.176 0 19.264-3.424 26.624-10.112 7.68-6.88 11.776-15.904 11.776-26.08V387.84c0-10.112-4.096-19.104-11.776-26.08a38.656 38.656 0 0 0-26.624-10.112z m72-241.28c21.824 0 30.176 8.256 30.176 29.568s-8.352 29.568-30.176 29.568H76.576c-21.824 0-30.176-8.256-30.176-29.568s8.352-29.568 30.176-29.568h870.848z"
fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+2
View File
@@ -788,6 +788,8 @@
"no_data": "No Data",
"loading_data": "Loading ...",
"show_error_detail": "Show error info",
"thousands_separator_setting": "Thousands Separator Setting",
"thousands_separator_display": "Apply Thousands Separator Display",
"log": {
"GENERATE_SQL": "Generate SQL",
"GENERATE_CHART": "Generate Chart Structure",
+2
View File
@@ -788,6 +788,8 @@
"no_data": "데이터가 없습니다",
"loading_data": "로딩 중 ...",
"show_error_detail": "구체적인 정보 보기",
"thousands_separator_setting": "천 단위 구분 기호 설정",
"thousands_separator_display": "천 단위 구분 기호 표시 적용",
"log": {
"GENERATE_SQL": "SQL 생성",
"GENERATE_CHART": "차트 구조 생성",
+2
View File
@@ -788,6 +788,8 @@
"no_data": "暂无数据",
"loading_data": "加载中...",
"show_error_detail": "查看具体信息",
"thousands_separator_setting": "千分位符设置",
"thousands_separator_display": "应用千分位符展示",
"log": {
"GENERATE_SQL": "生成 SQL",
"GENERATE_CHART": "生成图表结构",
+2
View File
@@ -788,6 +788,8 @@
"no_data": "暫無資料",
"loading_data": "載入中...",
"show_error_detail": "檢視具體資訊",
"thousands_separator_setting": "千分位符設定",
"thousands_separator_display": "套用千分位符顯示",
"log": {
"GENERATE_SQL": "產生 SQL",
"GENERATE_CHART": "產生圖表結構",
@@ -258,6 +258,8 @@ function stop() {
emits('stop')
}
const enableThousandsSeparatorList = ref<Array<string>>([])
onBeforeUnmount(() => {
stop()
})
@@ -274,6 +276,7 @@ defineExpose({ sendMessage, index: () => index.value, stop })
<template>
<BaseAnswer v-if="message" :message="message" :reasoning-name="reasoningName" :loading="_loading">
<ChartBlock
v-model:thousands-separator-list="enableThousandsSeparatorList"
style="margin-top: 6px"
:message="message"
:record-id="recordId"
@@ -257,6 +257,8 @@ function stop() {
emits('stop')
}
const enableThousandsSeparatorList = ref<Array<string>>([])
onBeforeUnmount(() => {
stop()
})
@@ -276,6 +278,7 @@ defineExpose({ sendMessage, index: () => index.value, chatList: () => _chatList,
<ChartBlock
v-if="message.record?.predict_data?.length > 0 && message.record?.data"
ref="chartBlockRef"
v-model:thousands-separator-list="enableThousandsSeparatorList"
style="margin-top: 12px"
:record-id="recordId"
:message="message"
@@ -20,6 +20,7 @@ import icon_window_max_outlined from '@/assets/svg/icon_window-max_outlined.svg'
import icon_window_mini_outlined from '@/assets/svg/icon_window-mini_outlined.svg'
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
import ICON_STYLE from '@/assets/svg/icon_style-set_outlined.svg'
import THOUSAND_SEPARATOR from '@/assets/svg/chart/icon-thousand-separator.svg'
import { useI18n } from 'vue-i18n'
import SQLComponent from '@/views/chat/component/SQLComponent.vue'
import { useAssistantStore } from '@/stores/assistant'
@@ -38,6 +39,7 @@ const props = withDefaults(
chatType?: ChartTypes
enlarge?: boolean
loadingData?: boolean
thousandsSeparatorList?: Array<string>
}>(),
{
recordId: undefined,
@@ -45,6 +47,7 @@ const props = withDefaults(
chatType: undefined,
enlarge: false,
loadingData: false,
thousandsSeparatorList: () => [],
}
)
@@ -52,7 +55,7 @@ const { copy } = useClipboard({ legacy: true })
const loading = ref<boolean>(false)
const { t } = useI18n()
const addViewRef = ref(null)
const emits = defineEmits(['exitFullScreen'])
const emits = defineEmits(['exitFullScreen', 'update:thousandsSeparatorList'])
const dataObject = computed<{
fields: Array<string>
@@ -364,6 +367,20 @@ watch(
}
}
)
const enableThousandsSeparatorList = computed({
get() {
return props.thousandsSeparatorList
},
set(v) {
emits('update:thousandsSeparatorList', v)
},
})
const optionList = ref<Array<{ name: string; value: string }>>([])
function getBaseAxis() {
optionList.value = chartRef.value?.getBaseAxis()
}
</script>
<template>
@@ -390,7 +407,7 @@ watch(
:chart-type="chartType"
:title="t('chat.type')"
@type-change="onTypeChange"
></ChartPopover>
/>
</el-tooltip>
<el-tooltip
@@ -412,23 +429,58 @@ watch(
</el-tooltip>
</div>
<div v-if="currentChartType !== 'table'" class="chart-select-container">
<div class="chart-select-container">
<template v-if="currentChartType !== 'table'">
<el-tooltip
effect="dark"
:offset="8"
:content="showLabel ? t('chat.hide_label') : t('chat.show_label')"
placement="top"
>
<el-button
class="tool-btn"
:class="{ 'chart-active': showLabel }"
text
@click="showLabel = !showLabel"
>
<el-icon size="16">
<ICON_STYLE />
</el-icon>
</el-button>
</el-tooltip>
</template>
<el-tooltip
effect="dark"
:offset="8"
:content="showLabel ? t('chat.hide_label') : t('chat.show_label')"
:content="t('chat.thousands_separator_setting')"
placement="top"
>
<el-button
class="tool-btn"
:class="{ 'chart-active': showLabel }"
text
@click="showLabel = !showLabel"
>
<el-icon size="16">
<ICON_STYLE />
</el-icon>
</el-button>
<div>
<el-popover placement="bottom" trigger="click">
<template #reference>
<el-button class="tool-btn" text @click="getBaseAxis">
<el-icon size="16">
<THOUSAND_SEPARATOR />
</el-icon>
</el-button>
</template>
<label style="font-weight: 500; line-height: 28px">
{{ t('chat.thousands_separator_display') }}
</label>
<el-checkbox-group
v-model="enableThousandsSeparatorList"
style="display: flex; flex-direction: column"
>
<el-checkbox
v-for="option in optionList"
:key="option.value"
size="large"
:label="option.name"
:value="option.value"
/>
</el-checkbox-group>
</el-popover>
</div>
</el-tooltip>
</div>
@@ -538,6 +590,7 @@ watch(
:data="data"
:loading-data="loadingData"
:show-label="showLabel"
:thousands-separator-list="enableThousandsSeparatorList"
/>
</div>
<div v-if="dataObject.limit" class="over-limit-hint">
@@ -556,7 +609,8 @@ watch(
body-class="chart-fullscreen-dialog-body"
>
<ChartBlock
v-if="dialogVisible"
v-if="dialogVisible && !enlarge"
v-model:thousands-separator-list="enableThousandsSeparatorList"
:message="message"
:record-id="recordId"
:is-predict="isPredict"
@@ -4,6 +4,7 @@ export interface ChartAxis {
type?: 'x' | 'y' | 'series' | 'other-info'
'multi-quota'?: boolean
hidden?: boolean
formatNumber?: boolean
}
export interface ChartData {
@@ -18,15 +19,17 @@ export abstract class BaseChart {
axis: Array<ChartAxis> = []
data: Array<ChartData> = []
showLabel: boolean = false
formatNumberFields: Array<string> = []
constructor(id: string, name: string) {
this.id = id
this._name = name
}
init(axis: Array<ChartAxis>, data: Array<ChartData>): void {
init(axis: Array<ChartAxis>, data: Array<ChartData>, formatNumberFields: Array<string>): void {
this.axis = axis
this.data = data
this.formatNumberFields = formatNumberFields
}
abstract render(): void
@@ -3,6 +3,7 @@ import { computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
import { getChartInstance } from '@/views/chat/component/index.ts'
import type { BaseChart, ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import { useEmitt } from '@/utils/useEmitt.ts'
import { filter, includes } from 'lodash-es'
const params = withDefaults(
defineProps<{
@@ -15,6 +16,7 @@ const params = withDefaults(
series?: Array<ChartAxis>
multiQuotaName?: string | undefined
showLabel?: boolean
thousandsSeparatorList?: Array<string>
}>(),
{
data: () => [],
@@ -24,6 +26,7 @@ const params = withDefaults(
series: () => [],
multiQuotaName: undefined,
showLabel: false,
thousandsSeparatorList: () => [],
}
)
@@ -34,10 +37,19 @@ const chartId = computed(() => {
const axis = computed(() => {
const _list: Array<ChartAxis> = []
params.columns.forEach((column) => {
_list.push({ name: column.name, value: column.value })
_list.push({
name: column.name,
value: column.value,
formatNumber: includes(params.thousandsSeparatorList, column.value),
})
})
params.x.forEach((column) => {
_list.push({ name: column.name, value: column.value, type: 'x' })
_list.push({
name: column.name,
value: column.value,
type: 'x',
formatNumber: includes(params.thousandsSeparatorList, column.value),
})
})
params.y.forEach((column) => {
_list.push({
@@ -45,10 +57,16 @@ const axis = computed(() => {
value: column.value,
type: 'y',
'multi-quota': column['multi-quota'],
formatNumber: includes(params.thousandsSeparatorList, column.value),
})
})
params.series.forEach((column) => {
_list.push({ name: column.name, value: column.value, type: 'series' })
_list.push({
name: column.name,
value: column.value,
type: 'series',
formatNumber: includes(params.thousandsSeparatorList, column.value),
})
})
if (params.multiQuotaName) {
_list.push({
@@ -67,16 +85,17 @@ function renderChart() {
chartInstance = getChartInstance(params.type, chartId.value)
if (chartInstance) {
chartInstance.showLabel = params.showLabel
chartInstance.init(axis.value, params.data)
chartInstance.init(axis.value, params.data, params.thousandsSeparatorList)
chartInstance.render()
}
}
watch(
() => params.showLabel,
() => [params.showLabel, params.thousandsSeparatorList],
() => {
renderChart()
}
},
{ deep: true }
)
function destroyChart() {
@@ -93,6 +112,10 @@ function getExcelData() {
}
}
function getBaseAxis() {
return filter(axis.value, (a) => !a.hidden)
}
useEmitt({
name: 'view-render-all',
callback: renderChart,
@@ -107,6 +130,7 @@ defineExpose({
renderChart,
destroyChart,
getExcelData,
getBaseAxis,
})
onMounted(() => {
@@ -5,14 +5,21 @@ import { computed, nextTick, ref } from 'vue'
import type { ChartTypes } from '@/views/chat/component/BaseChart.ts'
import { useI18n } from 'vue-i18n'
const props = defineProps<{
id?: number | string
chartType: ChartTypes
message: ChatMessage
data: Array<{ [key: string]: any }>
loadingData?: boolean
showLabel?: boolean
}>()
const props = withDefaults(
defineProps<{
id?: number | string
chartType: ChartTypes
message: ChatMessage
data: Array<{ [key: string]: any }>
loadingData?: boolean
showLabel?: boolean
thousandsSeparatorList?: Array<string>
}>(),
{
id: 'default_chat_id',
thousandsSeparatorList: () => [],
}
)
const { t } = useI18n()
@@ -102,6 +109,9 @@ defineExpose({
onTypeChange,
getViewInfo,
getExcelData,
getBaseAxis: () => {
return chartRef.value?.getBaseAxis() ?? []
},
})
</script>
@@ -119,6 +129,7 @@ defineExpose({
:data="data"
:multi-quota-name="multiQuotaName"
:show-label="showLabel"
:thousands-separator-list="thousandsSeparatorList"
/>
<el-empty v-else :description="loadingData ? t('chat.loading_data') : t('chat.no_data')" />
</div>
@@ -13,8 +13,8 @@ export class Bar extends BaseG2Chart {
super(id, 'bar')
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
init(axis: Array<ChartAxis>, data: Array<ChartData>, formatNumberFields: Array<string>) {
super.init(axis, data, formatNumberFields)
const axes = getAxesWithFilter(this.axis)
@@ -106,7 +106,8 @@ export class Bar extends BaseG2Chart {
labelAutoWrap: true,
labelAutoEllipsis: true,
labelFormatter: (value: any) => {
return String(formatNumber(value))
const formatted = axes.y[0].formatNumber ? formatNumber(value) : value
return String(formatted)
},
},
},
@@ -124,15 +125,19 @@ export class Bar extends BaseG2Chart {
tooltip: { series: series.length > 0, shared: true },
},
tooltip: (data: any) => {
const v = y[0].formatNumber ? formatNumber(data[y[0].value]) : data[y[0].value]
if (series.length > 0) {
const s = series[0].formatNumber
? formatNumber(data[series[0].value])
: data[series[0].value]
return {
name: data[series[0].value],
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
name: s,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
} else {
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
}
},
@@ -144,7 +149,8 @@ export class Bar extends BaseG2Chart {
if (value === undefined || value === null) {
return ''
}
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
const v = y[0].formatNumber ? formatNumber(value) : value
return `${v}${_data.isPercent ? '%' : ''}`
},
position: (data: any) => {
if (data[y[0].value] < 0) {
@@ -13,8 +13,8 @@ export class Column extends BaseG2Chart {
super(id, 'column')
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
init(axis: Array<ChartAxis>, data: Array<ChartData>, formatNumberFields: Array<string>) {
super.init(axis, data, formatNumberFields)
const axes = getAxesWithFilter(this.axis)
@@ -97,7 +97,8 @@ export class Column extends BaseG2Chart {
y: {
title: false, // y[0].name,
labelFormatter: (value: any) => {
return String(formatNumber(value))
const formatted = axes.y[0].formatNumber ? formatNumber(value) : value
return String(formatted)
},
},
},
@@ -115,15 +116,19 @@ export class Column extends BaseG2Chart {
tooltip: { series: series.length > 0, shared: true },
},
tooltip: (data: any) => {
const v = y[0].formatNumber ? formatNumber(data[y[0].value]) : data[y[0].value]
if (series.length > 0) {
const s = series[0].formatNumber
? formatNumber(data[series[0].value])
: data[series[0].value]
return {
name: data[series[0].value],
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
name: s,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
} else {
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
}
},
@@ -135,7 +140,8 @@ export class Column extends BaseG2Chart {
if (value === undefined || value === null) {
return ''
}
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
const v = y[0].formatNumber ? formatNumber(value) : value
return `${v}${_data.isPercent ? '%' : ''}`
},
position: (data: any) => {
if (data[y[0].value] < 0) {
@@ -13,8 +13,8 @@ export class Line extends BaseG2Chart {
super(id, 'line')
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
init(axis: Array<ChartAxis>, data: Array<ChartData>, formatNumberFields: Array<string>) {
super.init(axis, data, formatNumberFields)
const axes = getAxesWithFilter(this.axis)
@@ -71,7 +71,8 @@ export class Line extends BaseG2Chart {
y: {
title: false, // y[0].name,
labelFormatter: (value: any) => {
return String(formatNumber(value))
const formatted = axes.y[0].formatNumber ? formatNumber(value) : value
return String(formatted)
},
},
},
@@ -98,7 +99,8 @@ export class Line extends BaseG2Chart {
if (value === undefined || value === null) {
return ''
}
return `${formatNumber(value)}${_data.isPercent ? '%' : ''}`
const v = y[0].formatNumber ? formatNumber(value) : value
return `${v}${_data.isPercent ? '%' : ''}`
},
style: {
dx: -10,
@@ -113,15 +115,19 @@ export class Line extends BaseG2Chart {
]
: [],
tooltip: (data: any) => {
const v = y[0].formatNumber ? formatNumber(data[y[0].value]) : data[y[0].value]
if (series.length > 0) {
const s = series[0].formatNumber
? formatNumber(data[series[0].value])
: data[series[0].value]
return {
name: data[series[0].value],
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
name: s,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
} else {
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
}
},
@@ -1,15 +1,19 @@
import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import type { G2Spec } from '@antv/g2'
import { checkIsPercent, formatNumber, getAxesWithFilter } from '@/views/chat/component/charts/utils.ts'
import {
checkIsPercent,
formatNumber,
getAxesWithFilter,
} from '@/views/chat/component/charts/utils.ts'
export class Pie extends BaseG2Chart {
constructor(id: string) {
super(id, 'pie')
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
init(axis: Array<ChartAxis>, data: Array<ChartData>, formatNumberFields: Array<string>) {
super.init(axis, data, formatNumberFields)
const { y, series } = getAxesWithFilter(this.axis)
if (series.length == 0 || y.length == 0) {
@@ -48,18 +52,25 @@ export class Pie extends BaseG2Chart {
{
position: 'spider',
text: (data: any) => {
return `${data[series[0].value]}: ${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`
const v = y[0].formatNumber ? formatNumber(data[y[0].value]) : data[y[0].value]
return `${data[series[0].value]}: ${v}${_data.isPercent ? '%' : ''}`
},
},
]
: [],
tooltip: {
title: (data: any) => data[series[0].value],
title: (data: any) => {
const s = series[0].formatNumber
? formatNumber(data[series[0].value])
: data[series[0].value]
return s
},
items: [
(data: any) => {
const v = y[0].formatNumber ? formatNumber(data[y[0].value]) : data[y[0].value]
return {
name: y[0].name,
value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`,
value: `${v}${_data.isPercent ? '%' : ''}`,
}
},
],
@@ -9,7 +9,7 @@ import {
TableSheet,
type SortFuncParam,
} from '@antv/s2'
import { debounce, filter } from 'lodash-es'
import { debounce, filter, includes } from 'lodash-es'
import { i18n } from '@/i18n'
import { formatNumber } from '@/views/chat/component/charts/utils.ts'
import '@antv/s2/dist/s2.min.css'
@@ -100,10 +100,11 @@ export class Table extends BaseChart {
}
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
init(axis: Array<ChartAxis>, data: Array<ChartData>, formatNumberFields: Array<string>) {
super.init(
filter(axis, (a) => !a.hidden), //隐藏多指标的other-info列
data
data,
formatNumberFields
)
const s2DataConfig: S2DataConfig = {
@@ -122,7 +123,7 @@ export class Table extends BaseChart {
field: a.value,
name: a.name,
formatter: (value: any) => {
const formatted = formatNumber(value)
const formatted = a.formatNumber ? formatNumber(value) : value
return String(formatted)
},
}
@@ -201,7 +202,9 @@ export class Table extends BaseChart {
container.style.fontSize = '14px'
container.style.whiteSpace = 'pre-wrap'
const formattedValue = formatNumber(meta.fieldValue)
const formattedValue = includes(this.formatNumberFields, meta.valueField)
? formatNumber(meta.fieldValue)
: meta.fieldValue
const text = document.createTextNode(String(formattedValue))
container.appendChild(text)