diff --git a/bricks/build.sh b/bricks/build.sh
index c14d71f..d29f95a 100755
--- a/bricks/build.sh
+++ b/bricks/build.sh
@@ -11,7 +11,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \
llm.js websocket.js datarow.js tabular.js continueaudio.js \
line.js pie.js bar.js gobang.js period.js iconbarpage.js \
keypress.js asr.js webspeech.js countdown.js progressbar.js \
- qaframe.js svg.js videoplayer.js "
+ qaframe.js svg.js videoplayer.js sctter.js radar.js kline.js heatmap.js map.js"
echo ${SOURCES}
cat ${SOURCES} > ../dist/bricks.js
# uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js
diff --git a/bricks/heatmap.js b/bricks/heatmap.js
new file mode 100644
index 0000000..748ecd6
--- /dev/null
+++ b/bricks/heatmap.js
@@ -0,0 +1,73 @@
+var bricks = window.bricks || {};
+
+bricks.ChartHeatmap = class extends bricks.EchartsExt {
+ /*
+ 数据格式:
+ [
+ { x: '周一', y: '上午', value: 120 },
+ { x: '周二', y: '下午', value: 90 }, ...
+ ]
+
+ 参数:
+ {
+ data_url,
+ xField: 'x', // X 轴字段
+ yField: 'y', // Y 轴字段
+ valueField: 'value'
+ }
+ */
+ setup_options(data) {
+ const xField = this.xField || 'x';
+ const yField = this.yField || 'y';
+ const valueField = this.valueField || 'value';
+
+ const xs = [...new Set(data.map(d => d[xField]))];
+ const ys = [...new Set(data.map(d => d[yField]))];
+
+ const map = {};
+ ys.forEach(y => map[y] = {});
+ data.forEach(d => {
+ map[d[yField]][d[xField]] = d[valueField];
+ });
+
+ const heatmapData = [];
+ for (let j = 0; j < ys.length; j++) {
+ const y = ys[j];
+ for (let i = 0; i < xs.length; i++) {
+ const x = xs[i];
+ heatmapData.push([i, j, map[y][x] || 0]);
+ }
+ }
+
+ return {
+ tooltip: { position: 'top' },
+ grid: { height: '80%', top: '10%' },
+ xAxis: {
+ type: 'category',
+ data: xs,
+ splitArea: { show: true }
+ },
+ yAxis: {
+ type: 'category',
+ data: ys,
+ splitArea: { show: true }
+ },
+ visualMap: {
+ min: 0,
+ max: Math.max(...data.map(d => d[valueField])),
+ calculable: true,
+ orient: 'horizontal',
+ left: 'center',
+ bottom: '5%'
+ },
+ series: [{
+ type: 'heatmap',
+ data: heatmapData,
+ label: { show: false },
+ emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.5)' } }
+ }]
+ };
+ }
+};
+
+bricks.Factory.register('ChartHeatmap', bricks.ChartHeatmap);
diff --git a/bricks/kline.js b/bricks/kline.js
new file mode 100644
index 0000000..c1b9493
--- /dev/null
+++ b/bricks/kline.js
@@ -0,0 +1,49 @@
+var bricks = window.bricks || {};
+
+bricks.ChartKLine = class extends bricks.EchartsExt {
+ /*
+ 数据格式:
+ [
+ { name: '2025-04-01', open: 2100, close: 2150, low: 2080, high: 2160 },
+ ...
+ ]
+
+ 参数:
+ {
+ data_url,
+ nameField: 'name', // 时间轴字段
+ valueFields: ['open','close','low','high']
+ }
+ */
+ setup_options(data) {
+ const { nameField } = this;
+ const categories = data.map(d => d[nameField]);
+ const kData = data.map(d => [d.open, d.close, d.low, d.high]);
+
+ return {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: { type: 'cross' }
+ },
+ xAxis: {
+ type: 'category',
+ data: categories,
+ scale: true
+ },
+ yAxis: {
+ type: 'value',
+ scale: true
+ },
+ series: [{
+ type: 'candlestick',
+ data: kData
+ }],
+ dataZoom: [
+ { type: 'inside' },
+ { type: 'slider' }
+ ]
+ };
+ }
+};
+
+bricks.Factory.register('ChartKLine', bricks.ChartKLine);
diff --git a/bricks/map.js b/bricks/map.js
new file mode 100644
index 0000000..8e951d6
--- /dev/null
+++ b/bricks/map.js
@@ -0,0 +1,65 @@
+var bricks = window.bricks || {};
+
+bricks.ChartMap = class extends bricks.EchartsExt {
+ /*
+ 数据格式:
+ [
+ { name: '北京', value: 120 },
+ { name: '上海', value: 90 }, ...
+ ]
+
+ 参数:
+ {
+ data_url,
+ nameField: 'name',
+ valueField: 'value',
+ map_name: 'china', // 对应 echarts.registerMap 注册的地图名
+ map_options: {} // 可选自定义样式
+ }
+ */
+ async setup_options(data) {
+ const { nameField, valueField = 'value', map_name = 'china', map_options } = this;
+
+ const mapData = data.map(d => ({
+ name: d[nameField],
+ value: d[valueField]
+ }));
+
+ return {
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}: {c}'
+ },
+ visualMap: {
+ min: 0,
+ max: Math.max(...mapData.map(d => d.value)),
+ left: 'left',
+ top: 'bottom',
+ text: ['高', '低'],
+ calculable: true
+ },
+ series: [{
+ name: '数据',
+ type: 'map',
+ map: map_name,
+ roam: false,
+ label: { show: true, color: '#fff' },
+ itemStyle: {
+ areaColor: '#eee',
+ borderColor: '#444'
+ },
+ emphasis: {
+ label: { color: '#fff' },
+ itemStyle: { areaColor: '#e09191' }
+ },
+ data: mapData
+ }],
+ ...(map_options || {})
+ };
+ }
+};
+
+bricks.Factory.register('ChartMap', bricks.ChartMap);
+/*
+💡 使用前需通过 `echarts.registerMap('china', geoJson)` 注册地图数据。
+*/
diff --git a/bricks/radar.js b/bricks/radar.js
new file mode 100644
index 0000000..bf66f5a
--- /dev/null
+++ b/bricks/radar.js
@@ -0,0 +1,54 @@
+var bricks = window.bricks || {};
+
+bricks.ChartRadar = class extends bricks.EchartsExt {
+ /*
+ 数据格式示例:
+ [
+ { name: '张三', indicator1: 80, indicator2: 60, ... },
+ { name: '李四', indicator1: 70, indicator2: 90, ... }
+ ]
+
+ 参数:
+ {
+ data_url,
+ data_params,
+ nameField: 'name',
+ valueFields: ['indicator1', 'indicator2', ...],
+ radar_options: { // 自定义雷达配置
+ indicator: [
+ { name: '销售', max: 100 },
+ { name: '管理', max: 100 }, ...
+ ]
+ }
+ }
+ */
+ setup_options(data) {
+ const { nameField, valueFields } = this;
+ const series = [];
+ const indicator = this.radar_options?.indicator || valueFields.map(f => ({ name: f, max: 100 }));
+
+ data.forEach(item => {
+ series.push({
+ name: item[nameField],
+ type: 'radar',
+ data: [
+ {
+ value: valueFields.map(f => item[f]),
+ name: item[nameField]
+ }
+ ]
+ });
+ });
+
+ return {
+ tooltip: { trigger: 'item' },
+ legend: { data: data.map(d => d[nameField]) },
+ radar: { indicator },
+ series
+ };
+ }
+};
+
+bricks.Factory.register('ChartRadar', bricks.ChartRadar);
+/*
+*/
diff --git a/bricks/scatter.js b/bricks/scatter.js
new file mode 100644
index 0000000..6f83f28
--- /dev/null
+++ b/bricks/scatter.js
@@ -0,0 +1,98 @@
+var bricks = window.bricks || {};
+
+bricks.ChartScatter = class extends bricks.EchartsExt {
+ /*
+ * opts:
+ * {
+ * data_url,
+ * data_params,
+ * method,
+ * user_data,
+ * nameField: 可选,用于标签
+ * xField: X坐标字段
+ * yField: Y坐标字段
+ * sizeField?: 气泡大小字段(可选)
+ * categoryField?: 分组字段(用于不同颜色)
+ * }
+ */
+
+ setup_options(data) {
+ let seriesMap = {};
+
+ if (this.categoryField) {
+ // 按 categoryField 分组
+ data.forEach(item => {
+ const cat = item[this.categoryField] || 'default';
+ if (!seriesMap[cat]) {
+ seriesMap[cat] = {
+ name: cat,
+ type: 'scatter',
+ data: []
+ };
+ }
+ seriesMap[cat].data.push([
+ item[this.xField],
+ item[this.yField],
+ item[this.nameField] || null,
+ item[this.sizeField] || 1
+ ]);
+ });
+ } else {
+ // 单一系列
+ const seriesData = data.map(item => [
+ item[this.xField],
+ item[this.yField],
+ item[this.nameField] || null,
+ item[this.sizeField] || 1
+ ]);
+
+ seriesMap['scatter'] = {
+ type: 'scatter',
+ data: seriesData
+ };
+ }
+
+ const series = Object.values(seriesMap);
+ const xAxisName = this.xField;
+ const yAxisName = this.yField;
+
+ return {
+ tooltip: {
+ trigger: 'axis',
+ formatter: function(params) {
+ const p = params[0];
+ return `${p.seriesName}
${xAxisName}: ${p.value[0]}
${yAxisName}: ${p.value[1]}`;
+ }
+ },
+ legend: {
+ data: Object.keys(seriesMap)
+ },
+ xAxis: {
+ type: 'value',
+ name: xAxisName
+ },
+ yAxis: {
+ type: 'value',
+ name: yAxisName
+ },
+ series: series
+ };
+ }
+};
+
+// 注册工厂
+bricks.Factory.register('ChartScatter', bricks.ChartScatter);
+
+/*
+new bricks.ChartScatter({
+ user_data: [
+ { x: 10, y: 20, size: 30, group: 'Group A', label: 'Point 1' },
+ { x: 15, y: 25, size: 40, group: 'Group B', label: 'Point 2' }
+ ],
+ xField: 'x',
+ yField: 'y',
+ sizeField: 'size',
+ categoryField: 'group',
+ nameField: 'label'
+});
+*/