3

Тип диаграммы: кольцевая.

Особенность: в зависимости от процентов меняется цвет диаграммы и ее заполнение соответственно.

Характеристики: меньше 30% - красный, от 31 до 75 - желтый, от 76 до 100 - зеленый.

Визуальный пример:

кольцевая диаграмма

Разметка в основном компоненте:

<div class="medicine-medical">
  <div class="medicine-medical_transform transform">
    <div class="notransform">
      <span>Медосмотр</span>
    </div>
  </div>
  <div class="medicine-medical_block">
    <chart :userMedicalMedical="+worker.medinspection"/>
      <div class="img" ref="imgMedical"></div>
      <div class="data" ref="dataMedical"><p>{{worker.medinspection}} дн.</p></div>
  </div>
</div>

Код из компонента с диаграммой:

<template>
  <div class="chart-window">
    <svg width='60px' height='60px'>
      <circle cx='25' cy='25' r='41%' fill='transparent' stroke-width='1'></circle>
      <circle cx='25' cy='25' r='37%' fill='transparent' stroke-width='5'></circle>
      <circle ref="diagrammMin" cx='25' cy='25' r='35%' fill='transparent' stroke-width='5'></circle>
      <circle ref="diagramm" cx='25' cy='25' r='35%' fill='transparent' stroke-width='5'></circle>
    </svg>
  </div>
</template>

<script>
  export default {
    name: "employeeCardChart",
    props: {
      userMedicalMedical: Number,
      percent: Number,
      userMedicalReplacement: Array,
      training: Object,
      worker: Object
    },
    data: function () {
      return {
        warning: false,
        danger: false,
        flor: false,
        perpetual: false
      }
    },
    methods: {
      /***
       * функция checkData - проверяет наличие полей и в дальнейшем, если  есть, тогда изменяет стили диаграммы
       * Выходные параметры :
       *  this.warning,this.danger,this,flor - ячейки дял хранения булевых значений -
       *    в дальнейшем нужны для определения приоритета оповещения
       *  diagramm - переменная содержащая в себе атрибут refs диаграммы ( участок, который изменяется от данных -
       *    цвет и положение )
       *
       */
   checkData() {
        // если есть this.userMedicalMedical - МЕДОСМОТР
        if (this.userMedicalMedical) {
          let diagrammMin = this.$refs.diagrammMin;
          let diagramm = this.$refs.diagramm;
          if (this.userMedicalMedical >= 30) {
            diagramm.style.strokeDasharray = '132 131';
            diagramm.style.stroke = 'rgba(50, 50, 60, 1)'
          } else if (this.userMedicalMedical <= 29 && this.userMedicalMedical >= 21) {
            diagrammMin.style.stroke = 'rgba(239, 127, 26, 1';                        // для внутреннего бордера
            diagramm.style.stroke = 'rgb(230, 230, 230)';                           //для внешнего бордера
            diagramm.style.strokeDasharray = '78 41';                             // положение внешнего бордера
            this.$refs.diagrammMin.style.strokeDasharray = '38 41.5';
          } else if (this.userMedicalMedical <= 20 && this.userMedicalMedical >= 14) {
            diagrammMin.style.stroke = 'rgba(239, 127, 26, 1';                        // для внутреннего бордера
            diagramm.style.stroke = 'rgb(230, 230, 230)';                           //для внешнего бордера
            this.$refs.diagrammMin.style.strokeDasharray = '63 3.8';
            diagramm.style.strokeDasharray = '65 67';
          } else if (this.userMedicalMedical <= 13 && this.userMedicalMedical >= 7) {
            diagrammMin.style.stroke = 'rgba(239, 127, 26, 1';                        // для внутреннего бордера
            diagramm.style.stroke = 'rgb(230, 230, 230)';                           //для внешнего бордера
            this.$refs.diagrammMin.style.strokeDasharray = '30, 3.3';
            diagramm.style.strokeDasharray = '30 102';
          } else {
            diagramm.style.stroke = 'rgba(181, 90, 110, 1)';                        //для внешнего бордера
          }
        }
        if (this.percent) {
          console.log(this.percent, '-------');
          let breathalyzerUp = this.percent * 1.32;
          let breathalyzerOn = this.$refs.diagramm;
          // x.style.strokeDasharray = `${y} 131`
          breathalyzerOn.style.strokeDasharray = `${breathalyzerUp} 131`
        }
                mounted() {
      this.checkData();
    }
  }
</script>
Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384
  • нет на js не реализована в полной мере...(т.е есть что-то похожее но не рабочее...). Данные с БД приходят, но не получаеться настроить отображение(цвет не хочет меняться и заполнение всегда на 100%) – Pavel42rus Jun 18 '19 at 03:39
  • Уточняю... есть 2 компонента: 1 основной, а другой с диаграммой... подскажите какой кусок Вас интересует? – Pavel42rus Jun 18 '19 at 03:52
  • 'rgba(239, 127, 26, 1' - здесь у вас опечатка. И не только там. – Stepan Kasyanenko Jun 18 '19 at 05:26
  • да проблема в отрисовке... – Pavel42rus Jun 18 '19 at 06:05
  • опечатки поправил...(странно что storm не подчеркнул...) – Pavel42rus Jun 18 '19 at 06:07
  • в mounted код выполнится только после готовности всех компонентов один раз и не факт что в этот момент данные уже пришли с сервера, поэтому его надо переместить (метод) в computed и тогда по изменении данных из вне страница перерендерится – bobanobi4 Jun 18 '19 at 07:18
  • исправил, но не сработало... – Pavel42rus Jun 18 '19 at 07:49
  • https://ru.stackoverflow.com/a/962996/188366 – Stranger in the Q Jun 18 '19 at 17:08
  • https://ru.stackoverflow.com/q/868598/256824 –  Jun 25 '19 at 04:58

1 Answers1

3

Упрощенный вариант, когда все стили для диаграммы находятся в вычисляемом свойстве, т.е. в поле computed. Это позволит вам самостоятельно решать, какие атрибуты вынести в разметку, а какие атрибуты должны зависеть от входных параметров props. Демо:

// Отключим ненужные для примера
// сообщения в консоли.
Vue.config.productionTip = false;
Vue.config.devtools = false;

// Компонент круговой диаграммы. const Chart = { // Имя компонента. name: 'chart', // Свойства, передаваемые от родительского компонента. props: { medinspection: Number }, // Вычисляемые свойства. computed: { // Процент заполненности диаграммы, но без знака %. percent() { return this.$props.medinspection; }, // Стили для диаграммы. diagramStyle() { // Определим локально для удобства. const percent = this.percent;

  // Примем длину окружности за константу.
  const length = 100;

  // Примем радиус окружности за константу.
  const radius = length / (Math.PI * 2)

  // Штрих - заполненная часть окружности.
  const dash = percent;

  // Промежуток - соответственно, не заполненная.
  const space = length - dash;

  // Цвет линии по умолчанию.
  let stroke = 'rgb(255, 121, 121)';

  if (percent &gt; 75) {
    stroke = 'rgb(178, 214, 60)';
  } else if (percent &gt; 30) {
    stroke = 'rgb(241, 196, 15)'
  }

  return {
    'cx': 21, // координата центра окружности по оси абсцисс
    'cy': 21, // координата центра окружности по оси ординат
    'r': radius, // радиус окружности или просто: 15.91549431
    'fill': 'none', // заливка элемента
    'stroke': stroke, // цвет линии
    'stroke-width': 6, // толщина линии
    'stroke-dasharray': `${dash} ${space}`, // штрих и промежуток
    'stroke-dashoffset': 0 // смещение пунктирной обводки
  }
}

}, // Шаблон компонента. template: &lt;div class="chart__window"&gt; &lt;!-- viewBox - список из четырех чисел min-x, min-y, width и height, которые определяют прямоугольник в пользовательском пространстве. Должны быть удвоенными cx &amp; cy и больше диаметра круга. --&gt; &lt;svg width="100%" height="100%" viewBox="0 0 42 42"&gt; &lt;circle v-bind="diagramStyle"&gt;&lt;/circle&gt; &lt;/svg&gt; &lt;span class="circle__percent"&gt;{{ percent }}%&lt;/span&gt; &lt;/div&gt;, }

// Создаем экземпляр приложения. new Vue({ // Корневой элемент, определяется по css селектору. el: '#app', // Локальная регистрация компонентов. components: { 'chart': Chart }, // Исходные данные. Модели. data: { worker: { medinspection: 10 } } });

.chart__window {
  width: 88px;
  height: 88px;
  position: relative;
}

.circle__percent {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.1rem;
  font-weight: 300;
  font-family: cursive;
}
<div id="app">
  <chart :medinspection="worker.medinspection"></chart>
  <input type="range" v-model.number="worker.medinspection" min="0" max="100" />
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Кольцевая диаграмма с анимацией

Данный вариант посложнее, но принцип построения схож с первым: выносим значения атрибутов в computed поле. Демо:

// Отключим ненужные для примера
// сообщения в консоли.
Vue.config.productionTip = false;
Vue.config.devtools = false;

// Компонент круговой диаграммы. const CircleChart = { // Имя компонента. name: 'circle-chart', // Свойства, передаваемые от родительского компонента. props: { // Процент заполненности диаграммы, но без знака %. percent: Number, name: String }, // Вычисляемые свойства. computed: { completed() { if (100 === this.percent) { return 'Full'; } else if (!this.percent) { return 'O_o'; }

  return this.percent + '%';
},
// Стили для диаграммы.
diagramStyle() {
  // Определим локально для удобства.
  const percent = this.percent;

  // Примем длину окружности за константу.
  const length = 100;

  // Примем радиус окружности за константу.
  const radius = length / (Math.PI * 2);

  // Штрих - заполненная часть окружности.
  const dash = percent;

  // Промежуток - соответственно, не заполненная часть.
  const space = length - dash;

  // Цвет линии по умолчанию.
  let stroke = 'rgb(79, 83, 87)';

  return {
    'cx': 21, // координата центра окружности по оси асбцисс
    'cy': 21, // координата центра окружности по оси ординат
    'r': radius, // радиус окружности или просто: 15.91549431
    'fill': 'none', // заливка элемента
    'stroke': stroke, // цвет линии
    'stroke-width': .5, // толщина линии
    'stroke-linecap': 'round', // форма кончика линии
    'stroke-dasharray': `${dash} ${space}`, // штрих и промежуток

    // Смещение для начальной точки линии - штриха.
    // Смещение высчитывается против часовой стрелки.
    // При нуле будет начинаться с 3-х часов.
    // Нам нужно, чтобы начало линии было на 12-ть часов.
    // Значит, смещаем на 1/4 длины окружности: 100/4 = 25.
    // `transform: rotate` теперь можем не использовать.
    'stroke-dashoffset': 25,
  }
},
// Стили для фоновой окружности.
// Унаследуем стили от наложенной диаграммы.
backgroundStyle() {
  return {
    ...this.diagramStyle,
    'stroke': 'rgb(204, 204, 204)',
    'stroke-width': .35,
    'stroke-dasharray': '100 0', // полный штрих, на весь круг
  }
},
animateDiagramStyle() {
  // Определим локально для удобства имя атрибута,
  // к которому будет применяться анимация.
  const attribute = 'stroke-dasharray';

  // Значения `from` &amp; `to` переставляются местами,
  // когда нужен `reverse` анимации.
  return {
    'attributeName': attribute,
    'from': '0 100',
    'to': this.diagramStyle[attribute],
    'dur': '2s',
    'begin': '0',
    'fill': 'freeze',
  }
}

}, // Шаблон компонента. :key задан только для демки, // для перерисовки компонента и // применения стилей анимации при изменении значений. template: `<div class="chart__window" :key="_uid + percent"> <!-- viewBox - список из четырех чисел min-x, min-y, width и height, которые определяют прямоугольник в пользовательском пространстве. Должны быть удвоенными cx & cy и больше диаметра круга. -->

&lt;svg class="circle-container" width="100%" height="100%" viewBox="0 0 42 42" xmlns="http://www.w3.org/2000/svg"&gt;
  &lt;circle v-bind="backgroundStyle" /&gt;
  &lt;circle v-bind="diagramStyle"&gt;
    &lt;animate v-bind="animateDiagramStyle" /&gt;
  &lt;/circle&gt;

  &lt;g class="circle-chart-info"&gt;
    &lt;text class="circle-chart-percent" x="21" y="15.5" alignment-baseline="central" text-anchor="middle" font-size="8"&gt;{{ completed }}&lt;/text&gt;
    &lt;text class="circle-chart-subline" x="21" y="20.5" alignment-baseline="central" text-anchor="middle" font-size="2"&gt;{{ name }}&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;

</div>`, }

// Создаем экземпляр приложения. new Vue({ // Корневой элемент, определяется по css селектору. el: '#app', // Локальная регистрация компонентов. components: { 'circle-chart': CircleChart }, // Исходные данные. Модели. data: { // Массив кольцевых диаграмм. charts: [{ percent: 18, name: 'Первая' }, { percent: 64, name: 'Другая' }, { percent: 100, name: 'Еще одна' }] } });

.container {
  display: flex;
  flex-flow: row nowrap;
  max-width: 800px;
  margin: 25px auto;
}

.chart__window {
  justify-content: space-around;
  width: 33%;
  position: relative;
  text-align: center;
}



/**
 * С текстом не разбирался.
 */
.circle-chart-info {
  animation: circle-chart-appear 2s forwards;
  opacity: 0;
  transform: translateY(0.3em);
}

@keyframes circle-chart-appear {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
<div id="app">
  <div class="container">
    <template v-for="item in charts">
      <div class="chart__window">
        <input type="range" v-model.number="item.percent" min="0" max="100" />
      </div>
    </template>
  </div>

  <div class="container">
    <template v-for="item in charts">
      <circle-chart v-bind="item"></circle-chart>
    </template>
  </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

NB К сожалению в svg я полный ноль.

  • Никогда не поздно начать изучать svg :-) По началу будет трудно, но всегда будет легче разобраться, зная основы SVG, почему фреймворки JS иногда, по непонятным причинам работают не так, как ожидалось. Посмотрите топик, если есть желание как работать с dasharray svg Помогите с dasharray и dashoffset – Alexandr_TT Jun 20 '19 at 04:15