如何在d3js中使用重复的字符串值创建x轴? [英] How to create x-axis with duplicated string values in d3js?

查看:91
本文介绍了如何在d3js中使用重复的字符串值创建x轴?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建x轴,可以在其中设置一定数量的重复STRING值.

以下是我可以使用的数据示例(工作日名称的首字母):

const chartData = [
  { xValue: 'M', yValue: 1 },
  { xValue: 'T', yValue: 2 },
  { xValue: 'W', yValue: 2 },
  { xValue: 'T', yValue: 4 },
  { xValue: 'F', yValue: 2 },
  { xValue: 'S', yValue: 1 },
  { xValue: 'S', yValue: 3 }];

以下是创建条形图的代码:

const chartTicks = 5;
const margin = { top: 30, right: 30, bottom: 50, left: 140 };
const yScaleMaxValuePercentage = 110;

const xValue = 'xValue';
const yValue = 'yValue';
const svg = d3.select('.bar-chart-horizontal');
const marginWidth = margin.left + margin.right;
const marginHeight = margin.top + margin.bottom;
const width = +svg.attr('width') - marginWidth;
const height = +svg.attr('height') - marginHeight;
const scaleX = d3.scaleBand().rangeRound([height, 0]);
const scaleY = d3.scaleLinear().rangeRound([0, width]);
const mainGroup = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
let paddingBetweenBars = 0.7;

function calculateScaleY(chartData) {
  const maxValueY = d3.max(chartData, (d) => (d[yValue]));
  return [0, Math.ceil(maxValueY * yScaleMaxValuePercentage / 100)];
}

function drawBarChart(chartData) {
  // Calculating - X and Y axis scale
  scaleY.domain(calculateScaleY(chartData));
  scaleX.domain(chartData.map((d) => (d[xValue])))
    .padding(paddingBetweenBars);

  // Draw - Y axis
  mainGroup.append('g')
    .attr('class', 'axis axis--y')
    .attr('transform', `translate(0,${height})`)
    .call(d3.axisBottom(scaleY).ticks(chartTicks));

  // Draw - X axis
  mainGroup.append('g')
    .attr('class', 'axis axis--x')
    .call(d3.axisLeft(scaleX))
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'end');

  // Draw - Bars
  mainGroup.append('g')
    .selectAll('.bar-chart-horizontal .bar')
    .data(chartData)
    .enter().append('rect')
    .attr('class', 'bar')
    .attr('y', (d) => (scaleX(d[xValue])))
    .attr('height', scaleX.bandwidth())
    .attr('x', 0)
    .attr('width', (d) => (scaleY(d[yValue])));
}

drawBarChart(chartData);

CodePen: http://codepen.io/Arthe/pen/qqRpgj

我的问题是,在当前版本的代码中,它在同一标签上创建了两个小节(它们彼此重叠),因为在数据中我们得到了双倍的"S"和"T".我希望x轴具有两个不同的"S"和"T"(7个x轴标签,而不是5个).我尝试了stackoverflow的一些想法,但是没有一个答案可以让您在轴上使用字符串值.据我所知,线性刻度不要让您使用字符串.如果我向数据添加ID并将其用作排序变量,则条形不再重叠,但是我在x轴上有ID,而不是字符串.我查看了d3js可以提供的所有比例,但没有找到比序数更好的比例.

也许值得一提的是,我不知道我将在数据中获取哪种类型的字符串,以及将有多少种字符串,我只知道重复的字符串不能堆叠,但具有单独的条形.

解决方案

(第一个解决方案)

一种可能的解决方案:在一天中使用不同的值,例如:

const chartData = [{ xValue: 'Mon', yValue: 1 },
    { xValue: 'Tue', yValue: 2 },
    { xValue: 'Wed', yValue: 2 },
    { xValue: 'Thu', yValue: 4 },
    { xValue: 'Fri', yValue: 2 },
    { xValue: 'Sat', yValue: 1 },
    { xValue: 'Sun', yValue: 3 }];

然后,在轴生成器中,使用tickFormatsubstring仅显示第一个字母:

.call(d3.axisLeft(scaleX).tickFormat(d=>d.substring(0,1)))

这是您的笔: http://codepen.io/anon/pen/zoNRYZ?editors = 1010

(第二个解决方案)

另一种解决方案是使用一个对象来定义您的刻度线:

var myTicks = {Mon: "M", Tue: "T", Wed: "W",Thu: "T", Fri: "F", Sat: "S", Sun: "S"};

然后,在您的轴上,使用该对象定义刻度线:

.call(d3.axisLeft(scaleX).tickFormat(d=>myTicks[d]))

这是演示:

 const chartData = [
  { xValue: 'Mon', yValue: 1 },
   { xValue: 'Tue', yValue: 2 },
  { xValue: 'Wed', yValue: 2 },
   { xValue: 'Thu', yValue: 4 },
  { xValue: 'Fri', yValue: 2 },
   { xValue: 'Sat', yValue: 1 },
   { xValue: 'Sun', yValue: 3 }];

var myTicks = {Mon: "M", Tue: "T", Wed: "W",Thu: "T", Fri: "F", Sat: "S", Sun: "S"};
                   
const chartTicks = 5;
const margin = { top: 30, right: 30, bottom: 50, left: 140 };
const yScaleMaxValuePercentage = 110;

const xValue = 'xValue';
const yValue = 'yValue';
const svg = d3.select('.bar-chart-horizontal');
const marginWidth = margin.left + margin.right;
const marginHeight = margin.top + margin.bottom;
const width = +svg.attr('width') - marginWidth;
const height = +svg.attr('height') - marginHeight;
const scaleX = d3.scaleBand().rangeRound([height, 0]);
const scaleY = d3.scaleLinear().rangeRound([0, width]);
const mainGroup = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
let paddingBetweenBars = 0.7;

function calculateScaleY(chartData) {
  const maxValueY = d3.max(chartData, (d) => (d[yValue]));
  return [0, Math.ceil(maxValueY * yScaleMaxValuePercentage / 100)];
}

function drawBarChart(chartData) {
  // Calculating - X and Y axis scale
  scaleY.domain(calculateScaleY(chartData));
  scaleX.domain(chartData.map((d) => (d[xValue])))
    .padding(paddingBetweenBars);

  // Draw - Y axis
  mainGroup.append('g')
    .attr('class', 'axis axis--y')
    .attr('transform', `translate(0,${height})`)
    .call(d3.axisBottom(scaleY).ticks(chartTicks));

  // Draw - X axis
  mainGroup.append('g')
    .attr('class', 'axis axis--x')
    .call(d3.axisLeft(scaleX).tickFormat(d=>myTicks[d]))
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'end');

  // Draw - Bars
  mainGroup.append('g')
    .selectAll('.bar-chart-horizontal .bar')
    .data(chartData)
    .enter().append('rect')
    .attr('class', 'bar')
    .attr('y', (d) => (scaleX(d[xValue])))
    .attr('height', scaleX.bandwidth())
    .attr('x', 0)
    .attr('width', (d) => (scaleY(d[yValue])));
}

drawBarChart(chartData); 

 <script src="https://d3js.org/d3.v4.min.js"></script>
<div>
<svg class="bar-chart-horizontal" width="600" height="400"></svg>
</div> 

(第三种解决方案)

EDIT 来回答您的您的要求是不可能的,在序数标度中,两个相同的字符串输入值被认为是相同的(相同的输入值将始终映射为相同的值产值).如果您有重复的字符串,但您事先不知道这些字符串,则几乎没有其他选择.其中之一是使用索引来定义您的域:

var ind = d3.range(chartData.length);
scaleX.domain(ind)

然后,在栏中:

.attr('y', (d,i) => (scaleX(i)))

最后,在轴生成器中:

.call(d3.axisLeft(scaleX).tickFormat((d,i)=>chartData[i].xValue))

这是另一个 笔: http://codepen .io/anon/pen/XNpZMg?editors = 0010

(第四种解决方案)

由于这是典型的 XY问题,我花了一些时间实现您真正想要的.此第四种解决方案可能是适合您的最佳解决方案.你说:

如果我向数据添加ID并将其用作排序变量,则条形不再重叠,但是我在x轴上有ID,而不是字符串

但这是不正确的.您可以随意放置任何内容.因此,该解决方案使用唯一的ID来制作图表,但是根据唯一的ID显示不同的刻度.这是具有唯一ID的数据:

const chartData = [
    { xValue: 'M', ID: "foo", yValue: 1 },
    { xValue: 'T', ID: "bar", yValue: 4 },
    { xValue: 'W',  ID: "baz",yValue: 2 },
    { xValue: 'T',  ID: "foobar",yValue: 2 },
    { xValue: 'F',  ID: "foobaz",yValue: 2 },
    { xValue: 'S',  ID: "barfoo",yValue: 3 },
    { xValue: 'S',  ID: "barbaz",yValue: 1 }];

我们使用ID制作图表:

.attr('y', (d) => (scaleX(d.ID)))

但是,在轴生成器上,我们使用xValue进行刻度:

.call(d3.axisLeft(scaleX).tickFormat(function(d) {
    var filtered = chartData.filter(function(e) {
        console.log(d);
        return e.ID === d
    });
    return filtered[0].xValue;
}));

以下是相应的笔: http://codepen.io/anon/pen/gLgjyW?editors = 0010

I want to create x-axis where I can set certain number of duplicated STRING values.

Here is example of data I could use for it (first letters of weekday names):

const chartData = [
  { xValue: 'M', yValue: 1 },
  { xValue: 'T', yValue: 2 },
  { xValue: 'W', yValue: 2 },
  { xValue: 'T', yValue: 4 },
  { xValue: 'F', yValue: 2 },
  { xValue: 'S', yValue: 1 },
  { xValue: 'S', yValue: 3 }];

Here is the code for creating bar-chart:

const chartTicks = 5;
const margin = { top: 30, right: 30, bottom: 50, left: 140 };
const yScaleMaxValuePercentage = 110;

const xValue = 'xValue';
const yValue = 'yValue';
const svg = d3.select('.bar-chart-horizontal');
const marginWidth = margin.left + margin.right;
const marginHeight = margin.top + margin.bottom;
const width = +svg.attr('width') - marginWidth;
const height = +svg.attr('height') - marginHeight;
const scaleX = d3.scaleBand().rangeRound([height, 0]);
const scaleY = d3.scaleLinear().rangeRound([0, width]);
const mainGroup = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
let paddingBetweenBars = 0.7;

function calculateScaleY(chartData) {
  const maxValueY = d3.max(chartData, (d) => (d[yValue]));
  return [0, Math.ceil(maxValueY * yScaleMaxValuePercentage / 100)];
}

function drawBarChart(chartData) {
  // Calculating - X and Y axis scale
  scaleY.domain(calculateScaleY(chartData));
  scaleX.domain(chartData.map((d) => (d[xValue])))
    .padding(paddingBetweenBars);

  // Draw - Y axis
  mainGroup.append('g')
    .attr('class', 'axis axis--y')
    .attr('transform', `translate(0,${height})`)
    .call(d3.axisBottom(scaleY).ticks(chartTicks));

  // Draw - X axis
  mainGroup.append('g')
    .attr('class', 'axis axis--x')
    .call(d3.axisLeft(scaleX))
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'end');

  // Draw - Bars
  mainGroup.append('g')
    .selectAll('.bar-chart-horizontal .bar')
    .data(chartData)
    .enter().append('rect')
    .attr('class', 'bar')
    .attr('y', (d) => (scaleX(d[xValue])))
    .attr('height', scaleX.bandwidth())
    .attr('x', 0)
    .attr('width', (d) => (scaleY(d[yValue])));
}

drawBarChart(chartData);

CodePen: http://codepen.io/Arthe/pen/qqRpgj

My problem is, that with current version of code, it creates two bars on the same label (they overlap each other), because we got double 'S' and double 'T' in the data. I want x-axis to have two different 'S' and 'T' (7 x-axis labels instead of 5). I tried some ideas from stackoverflow, but none of the answer let you use string values on the axis. Linear scale as far as I know, don't let you use strings. If I add IDs to data and use them as sorting variables, the bars dont overlap anymore but I have IDs on the x axis instead of strings. I looked on all scales d3js can provide and I didn't find better scale for it than ordinal one.

EDIT: Maybe its worth mentioning, that I don't know what type of strings I will get in data, and how many of them there will be, I just know that duplicated strings can't be stacked, but have separate bars.

解决方案

(first solution)

A possible (out of many) solution: Use different values for the days, like this:

const chartData = [{ xValue: 'Mon', yValue: 1 },
    { xValue: 'Tue', yValue: 2 },
    { xValue: 'Wed', yValue: 2 },
    { xValue: 'Thu', yValue: 4 },
    { xValue: 'Fri', yValue: 2 },
    { xValue: 'Sat', yValue: 1 },
    { xValue: 'Sun', yValue: 3 }];

And then, in the axis generator, show just the first letter, using tickFormat and substring:

.call(d3.axisLeft(scaleX).tickFormat(d=>d.substring(0,1)))

Here is your Pen: http://codepen.io/anon/pen/zoNRYZ?editors=1010

(second solution)

Another solution is using an object to define your ticks:

var myTicks = {Mon: "M", Tue: "T", Wed: "W",Thu: "T", Fri: "F", Sat: "S", Sun: "S"};

And then, in your axis, using that object to define the ticks:

.call(d3.axisLeft(scaleX).tickFormat(d=>myTicks[d]))

Here is the demo:

const chartData = [
  { xValue: 'Mon', yValue: 1 },
   { xValue: 'Tue', yValue: 2 },
  { xValue: 'Wed', yValue: 2 },
   { xValue: 'Thu', yValue: 4 },
  { xValue: 'Fri', yValue: 2 },
   { xValue: 'Sat', yValue: 1 },
   { xValue: 'Sun', yValue: 3 }];

var myTicks = {Mon: "M", Tue: "T", Wed: "W",Thu: "T", Fri: "F", Sat: "S", Sun: "S"};
                   
const chartTicks = 5;
const margin = { top: 30, right: 30, bottom: 50, left: 140 };
const yScaleMaxValuePercentage = 110;

const xValue = 'xValue';
const yValue = 'yValue';
const svg = d3.select('.bar-chart-horizontal');
const marginWidth = margin.left + margin.right;
const marginHeight = margin.top + margin.bottom;
const width = +svg.attr('width') - marginWidth;
const height = +svg.attr('height') - marginHeight;
const scaleX = d3.scaleBand().rangeRound([height, 0]);
const scaleY = d3.scaleLinear().rangeRound([0, width]);
const mainGroup = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
let paddingBetweenBars = 0.7;

function calculateScaleY(chartData) {
  const maxValueY = d3.max(chartData, (d) => (d[yValue]));
  return [0, Math.ceil(maxValueY * yScaleMaxValuePercentage / 100)];
}

function drawBarChart(chartData) {
  // Calculating - X and Y axis scale
  scaleY.domain(calculateScaleY(chartData));
  scaleX.domain(chartData.map((d) => (d[xValue])))
    .padding(paddingBetweenBars);

  // Draw - Y axis
  mainGroup.append('g')
    .attr('class', 'axis axis--y')
    .attr('transform', `translate(0,${height})`)
    .call(d3.axisBottom(scaleY).ticks(chartTicks));

  // Draw - X axis
  mainGroup.append('g')
    .attr('class', 'axis axis--x')
    .call(d3.axisLeft(scaleX).tickFormat(d=>myTicks[d]))
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'end');

  // Draw - Bars
  mainGroup.append('g')
    .selectAll('.bar-chart-horizontal .bar')
    .data(chartData)
    .enter().append('rect')
    .attr('class', 'bar')
    .attr('y', (d) => (scaleX(d[xValue])))
    .attr('height', scaleX.bandwidth())
    .attr('x', 0)
    .attr('width', (d) => (scaleY(d[yValue])));
}

drawBarChart(chartData);

<script src="https://d3js.org/d3.v4.min.js"></script>
<div>
<svg class="bar-chart-horizontal" width="600" height="400"></svg>
</div>

(third solution)

EDIT to answer your edit: what you're asking is not possible, in an ordinal scale two identical string input values are considered the same (the same input value will always be mapped to the same output value). If you have duplicated strings and you don't know the strings beforehand, there are few alternatives. One of them is using the indices to define your domain:

var ind = d3.range(chartData.length);
scaleX.domain(ind)

Then, in the bars:

.attr('y', (d,i) => (scaleX(i)))

And, finally, in the axis generator:

.call(d3.axisLeft(scaleX).tickFormat((d,i)=>chartData[i].xValue))

Here is another Pen: http://codepen.io/anon/pen/XNpZMg?editors=0010

(Forth solution)

As this was a typical XY problem, it took me some time to realize what you really want. This forth solution is probably the best one for your porposes. You said:

If I add IDs to data and use them as sorting variables, the bars dont overlap anymore but I have IDs on the x axis instead of strings

But this is not correct. You can put anything you want on the ticks. So, this solution uses unique IDs to make the chart, but displays different ticks, based on the unique IDs. This is your data with unique IDs:

const chartData = [
    { xValue: 'M', ID: "foo", yValue: 1 },
    { xValue: 'T', ID: "bar", yValue: 4 },
    { xValue: 'W',  ID: "baz",yValue: 2 },
    { xValue: 'T',  ID: "foobar",yValue: 2 },
    { xValue: 'F',  ID: "foobaz",yValue: 2 },
    { xValue: 'S',  ID: "barfoo",yValue: 3 },
    { xValue: 'S',  ID: "barbaz",yValue: 1 }];

We use the IDs to make the chart:

.attr('y', (d) => (scaleX(d.ID)))

But, on the axis generator, we use xValue to make the ticks:

.call(d3.axisLeft(scaleX).tickFormat(function(d) {
    var filtered = chartData.filter(function(e) {
        console.log(d);
        return e.ID === d
    });
    return filtered[0].xValue;
}));

Here is the corresponding Pen: http://codepen.io/anon/pen/gLgjyW?editors=0010

这篇关于如何在d3js中使用重复的字符串值创建x轴?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆